State Changes & State Views
EvidentSource’s programming model is two pure functions.
- State Changes:
decide(db, command, metadata) → { events, conditions }— handles a command, returns the events to append. - State Views:
evolve(state_view, events) → state— folds events into queryable materialized state.
That’s the whole model. No distributed transactions. No saga orchestrators. No eventual-consistency state machines you have to reason about.
decide: turning commands into events
Section titled “decide: turning commands into events”When a command arrives at EvidentSource, it’s routed to a State Change component — a WebAssembly module that exports a decide function. The function gets:
db, a handle for reading current state views to inform the decisioncommand, the input payload with type and subjectmetadata, provenance information (caller identity, correlation IDs, etc.)
It returns:
events, the list of events to append if the decision standsconditions, optimistic-concurrency predicates that must hold at append time
Because decide is a pure function of its inputs, it is trivially testable, trivially inspectable, and trivially generated by an LLM. Every decision is a read + a function call + an append — no locks, no transactions spanning services.
export const decide = (db, command, metadata) => { const todo = db.viewState("single-todo", { todoId: command.todoId }); if (!todo) throw StateChangeError.notFound("Todo not found");
return { events: [{ type: todo.completed ? "TodoUncompleted" : "TodoCompleted", subject: command.todoId, }], conditions: [unchangedSince(command.todoId, todo.revision)], };};The conditions field is how cross-entity invariants get enforced without distributed transactions — see Dynamic Consistency Boundaries.
evolve: folding events into state
Section titled “evolve: folding events into state”When events are appended, they fan out to every State View component that subscribes to them. Each view is a WebAssembly module exporting an evolve function:
export const evolve = (stateView, events) => { // fold `events` into `stateView`, return the new state};evolve is also a pure function. Given the same current state and the same events, it returns the same next state. That makes state views trivially reprojectable — if you change the shape of a view, EvidentSource can re-run evolve against the full event history and rebuild the view from scratch.
State views are what your app reads. The db handle passed to decide reads from state views, not from raw event storage. This keeps decide simple and makes the read path fast.
Why these two functions
Section titled “Why these two functions”Everything about EvidentSource follows from this split:
- Auditability — the events
decidereturns are the permanent record. State views are derived; you can always recompute them. Events are not. - AI-friendliness — pure functions are the ideal target for LLM code generation. The prompt doesn’t need to be brilliant; the function signature does most of the work.
- Simplicity at scale — there is nothing in the programming model that requires distributed coordination. You scale by running more containers.
- Hot reload — because components are WASM modules, you can version and swap them without restarting the runtime.
Where they run
Section titled “Where they run”State Changes and State Views are both WebAssembly components, compiled from the language of your choice using the EvidentSource SDK for that language. The same runtime executes them, sandboxed, co-located with the event store.
See the SDK docs for how to build components in Rust, Go, TypeScript, .NET, and Python.
Further reading
Section titled “Further reading”- Events & the System’s Narrative — why events are the right primitive.
- Bi-Temporal Indexing — querying the past, as the system saw it.
- Dynamic Consistency Boundaries — the pattern for cross-entity invariants.
- Decision Traces & AI-Readiness — why this matters for AI.