Captures
Chronicle.Captures.Capture provides a DSL for defining Chronicle captures, and Chronicle.Captures provides an idiomatic Elixir API for registering, starting, stopping, deleting, listing, and validating them.
Captures turn data that arrives from outside the event log — an external API, an inbound webhook, or another event store’s public events — into events appended to this event store. The DSL compiles at build time to Capture Declaration Language (CDL) text, the same text the kernel’s Workbench and SaveCapture accept. See the Chronicle documentation’s Capture Declaration Language reference for the full grammar.
Defining a capture
Section titled “Defining a capture”defmodule MyApp.Captures.InvoiceCapture do use Chronicle.Captures.Capture, name: "InvoiceCapture"
api_source("InvoicingApi", route: "/invoices", poll: "10m")
key("id")
append MyApp.Events.InvoiceStatusChanged, when: "status" do set(:status, "$.status") endenduse Chronicle.Captures.Captureregisters:name(defaults to the module’s last segment) and:id(defaults to a deterministic UUIDv5 derived from the module’s full name, so re-registering the same module updates the same kernel-side capture rather than creating a new one every deploy). Passstart: trueto haveChronicle.Captures.Registrarstart the capture once it has been saved.- Exactly one source is required:
api_source/1,2(polls a named External Service),webhook_source/1(an inbound webhook path), orevents_source/1(captures from another event store’s public events, given as a list of registeredChronicle.Events.EventTypemodules). key/1names the property path used to identify and diff a captured item. For an events source, use"$eventSourceId".append/2,3declares an event to append when itswhen:condition matches;set/2inside its block maps a target field of the event’s struct from a source expression ("$.path"for the current payload,"$context.<property>"for the capture or event context).
when: conditions
Section titled “when: conditions”append MyApp.Events.CustomerChanged, when: "firstName" do set(:first_name, "$.firstName")end
append MyApp.Events.CustomerChanged, when: {:any, ["firstName", "lastName"]} do set(:first_name, "$.firstName")end
append MyApp.Events.CustomerActivated, when: {:transition, "status", "inactive", "active"} do set(:status, "$.status")end
append MyApp.Events.ItemAdded, when: :added do set(:line_number, "$.lineNumber")end- a string — fires when that property changes
{:any, [...]}/{:all, [...]}— an “or”/“and” of several properties{:transition, property, from, to}— fires when a property moves between two specific values:added/:removed— fires when the item is first seen or disappears{:expression, "template"}— a raw CDL expression
Map operations
Section titled “Map operations”map do rename(:full_name, :fornavn) template(:greeting, "Hello, ${fornavn}") translate(:status, :status, [{"aktiv", "active"}, {"inaktiv", "inactive"}]) split(:full_name, by: ",", into: [:first_name, :last_name])endNested and children scopes
Section titled “Nested and children scopes”nested :billing_address do map do rename(:street, :"fakturaAdresse.gate") end
append MyApp.Events.BillingAddressChanged, when: "street" do set(:street, "$.street") endend
children :line_items, identified_by: :line_number do append MyApp.Events.ItemAdded, when: :added do set(:line_number, "$.lineNumber") endendRegistering a capture
Section titled “Registering a capture”Register a capture module explicitly with Chronicle.Client:
{Chronicle.Client, connection_string: "chronicle://localhost:35000", event_store: "my-store", captures: [MyApp.Captures.InvoiceCapture]}Or rely on otp_app: auto-discovery — modules that export __chronicle_capture__/1 are discovered automatically, the same way reactors, reducers, and webhooks are. Either way, Chronicle.Captures.Registrar saves every discoverable capture once the client’s connection reaches the :registered phase, and re-registers on every reconnect:
- It is idempotent: it compares the compiled declaration against what
Chronicle.Captures.list/1reports and skips a capture whose declaration already matches. - It warns, rather than fails, before saving over a capture that is
:startedand whose declaration changed — saving a started capture stops it on the kernel, so it comes back stopped. - It starts a capture only when it declares
start: true.
Managing a capture directly
Section titled “Managing a capture directly”{:ok, capture} = Chronicle.Captures.register(MyApp.Captures.InvoiceCapture):ok = Chronicle.Captures.start(MyApp.Captures.InvoiceCapture){:ok, captures} = Chronicle.Captures.list(){:ok, messages} = Chronicle.Captures.validate(MyApp.Captures.InvoiceCapture){:ok, events} = Chronicle.Captures.captured_events("InvoiceCapture"):ok = Chronicle.Captures.stop(MyApp.Captures.InvoiceCapture):ok = Chronicle.Captures.delete(MyApp.Captures.InvoiceCapture)Every function also accepts a raw capture id (a GUID string) instead of a module for start/2, stop/2, and delete/2, and raw CDL text instead of a module for validate/2 — useful for validating a declaration before it is ever saved.
Introspection
Section titled “Introspection”MyApp.Captures.InvoiceCapture.__chronicle_capture__(:id)MyApp.Captures.InvoiceCapture.__chronicle_capture__(:name)MyApp.Captures.InvoiceCapture.__chronicle_capture__(:declaration)MyApp.Captures.InvoiceCapture.__chronicle_capture__(:start?)