Event Processors
Client-side reducers that transform encrypted event streams into application state.
Event Sourcing
Decentrl applications are pure event processors. There is no backend database, no server-side state, no REST API returning "current data." Instead:
- Events are the source of truth — stored encrypted on your mediator
- Application state is a derived projection — built locally by applying events through reducers
- Reducers are pure functions — given the same events, they always produce the same state
This is the same pattern used by databases (write-ahead logs), distributed systems (event sourcing), and state management libraries (Redux). Decentrl applies it to encrypted, decentralized communication.
Defining a Chat Application
A chat application defines event types and how they reduce into state:
Application: ChatApp
Events:
chat.create → { id, participants, createdBy, createdAt }
chat.message → { id, chatId, content, sender, timestamp }
chat.reaction → { id, messageId, chatId, emoji, sender, removed? }
chat.presence → { timestamp } // ephemeral
chat.read → { chatId, timestamp }
State slices:
chats → reduced from chat.create (append if not duplicate)
messages → reduced from chat.message (grouped by chatId)
reactions → reduced from chat.reaction (add/remove by sender+emoji)
lastSeen → reduced from chat.presence (latest timestamp per DID)
readMarkers → reduced from chat.read (max timestamp per chatId)Idempotent Reducers
Because events may arrive out of order or be replayed during state reconstruction, all reducers must be idempotent. The same event applied twice should produce the same result:
reduce chat.message:
if messages[chatId] already contains event.id → return unchanged
else → append event to messages[chatId]
reduce chat.reaction:
if event.removed → filter out matching (sender, emoji)
if reactions[messageId] already contains (sender, emoji) → return unchanged
else → append reaction
reduce chat.read:
readMarkers[chatId] = max(existing, event.timestamp) // naturally idempotentDuplicate detection by event ID handles messages arriving twice. Using max() for timestamps naturally handles out-of-order delivery.
Beyond Chat
The same pattern works for any application:
Social network:
post.create → builds timeline
post.reaction → aggregates likes/reactions
follow.create → builds social graph
follow.remove → updates social graphCollaborative document:
doc.create → initializes document
doc.edit → applies operation (CRDT or OT)
doc.comment → builds comment threadMarketplace:
listing.create → adds product listing
listing.update → modifies price/availability
order.create → initiates transaction
order.status → tracks fulfillmentThe protocol doesn't care what your events represent. It handles identity, encryption, routing, and storage. Your application defines the schema and reducers — everything else is automatic.