Skip to content

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.

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 decision
  • command, the input payload with type and subject
  • metadata, provenance information (caller identity, correlation IDs, etc.)

It returns:

  • events, the list of events to append if the decision stands
  • conditions, 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.

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.

Everything about EvidentSource follows from this split:

  • Auditability — the events decide returns 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.

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.