Skip to content

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.

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.

command

returns an event

projection

query

reactor

command

Client

@command() class · handle()

Chronicle event log

@readModel() · @fromEvent

@reactor() · returns commands

The Library sample walks that loop. Registration returns the event:

Features/Authors/Registration/Registration.ts (excerpt)
@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:

Features/Authors/Listing/Listing.ts (excerpt)
@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.

  • 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.
PageUse it when you want to
Add event sourcingStart a local kernel, record a first event from a command, and query its projection
Registration optionsLook up the withChronicle options, configuration keys, and client ownership
The Cratis packageRegister Arc and Chronicle with one call from @cratis/cratis
Returning eventsReturn one event, a batch, or events next to a response
Event metadataSee what each appended event carries and where every value comes from
Resolving the event source IDChoose which event source an event is appended to, and route it
SubjectRecord whose personal data an event carries
ConcurrencyReject an append when the stream moved
Causation and auditingSee what the permanent causation chain records, and keep secrets out
Transactional commandsUnderstand the batch across nested commands and its failure rules
Read modelsServe projected state from queries
Read models in commandsDecide or validate from the state projected for the command’s key
When read model resolution failsUnderstand a rejected command that loads a read model
AggregatesDecide from one event source’s full history
ReactorsRun a follow-up command when an event is recorded
ComplianceKnow what the integration does, and does not do, for personal data
Code analysisSee which .NET ARCCHR diagnostics apply in TypeScript
Testing Chronicle commandsAssert returned events without a kernel
Testing against a kernelCheck projections and constraints against a live Chronicle kernel
Read consistencySelect passive reads or wait for observer completion after a write
  • 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 MongoReadModels document typed with readModel. Untyped raw documents, derived subtypes, and mapped objects are served as stored unless you call readModels.release yourself. 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 ARCCHR analyzers exist for TypeScript. See Code analysis.

Start with Add event sourcing, or run the Library sample to see a finished application.