The Information System: State, Context, and Views
In Happen, information flows naturally through the system in multiple complementary forms. The Information System builds on our core primitives—Nodes and Events—while providing powerful capabilities for managing, accessing, and transforming information across your application. This document explains the conceptual model and practical implementation of state, context, and views in Happen.
The Information System is built on three fundamental concepts:
State: What You Own
State is information that a node has authority over. It's the internal data that the node manages, updates, and is responsible for. State represents "what you know and control" within your domain.
State in Happen is:
Authoritative - The owning node is the single source of truth
Persisted - State can be saved and restored
Encapsulated - Direct modification is restricted to the owning node
Verifiable - State changes are tracked in the causal chain
// Basic state access
const orderNode = createNode("order-service");
// Read state directly
const state = orderNode.state.get();
const pendingOrders = Object.values(state.orders || {})
.filter(order => order.status === "pending");
// Transform state using a function approach
orderNode.state.set(state => {
return {
...state,
orders: {
...state.orders,
"order-123": {
customerId: "cust-456",
items: [{productId: "prod-789", quantity: 2}],
status: "pending"
}
}
};
});
State can also be accessed selectively, using transformations to extract only the required data:
// Focused state access
const activeOrderCount = orderNode.state.get(state =>
Object.values(state.orders || {})
.filter(order => order.status === "active")
.length
);
Global State Implementation
Happen's provides system-wide, crossboundary global state for universal data access throughout the unified event space. Global state is implemented using NATS Key-Value store and is accessible from all environments:
Unified Access: All nodes use the same
node.global
API regardless of environmentNative Transport Adapters: NATS provides native support for various transports:
TCP for server-to-server communication
WebSocket for browser and edge clients
TLS/WSS for secure communication
Consistent Experience: The same operations work identically across all environments
This approach leverages NATS' built-in transport capabilities to provide a seamless global state experience without requiring custom protocol adaptations.
For example, storing and retrieving data works the same way everywhere:
// Store data in global state
await node.global.set('user:profile:123', userProfile);
// Retrieve data from global state
const profile = await node.global.get('user:profile:123');
Context: What Happened and Why
Context is information that flows with events, providing essential metadata about causality, origin, and relationships. Context represents the "why" and "how" of information flow in the system.
Context in Happen is:
Causal - Tracks relationships between events
Automatic - Flows naturally with events
Enriched - Gains additional information as it flows
Structured - Organized into specific categories
// Event with context
orderNode.broadcast({
type: "order-created",
payload: {
orderId: "ORD-123",
items: [{ productId: "PROD-456", quantity: 2 }]
}
// Context is automatically managed by the framework
// context: {
// causal: {
// id: "evt-789", // Unique identifier
// sender: "order-service", // Origin node
// causationId: "evt-456", // Direct cause
// correlationId: "order-123", // Transaction identifier
// path: ["customer-node", "order-service"] // Event path
// },
// system: {
// environment: "production",
// region: "us-west"
// },
// user: {
// id: "user-123",
// permissions: ["create-order"]
// }
// }
});
Context is system-managed and automatically flows with events, requiring no manual manipulation.
Views: Windows Into Other Nodes
Views in Happen provide a window into other nodes' state, enabling coordinated operations across node boundaries. While conceptually simple, views are implemented using an elegant recursive traversal approach that leverages JavaScript's reference passing.
The Recursive Reference Collection Mechanism
At the core of Happen's view implementation is a recursive traversal of the node graph, combined with a shared reference mechanism.
When a node accesses a view, the system:
Creates a shared reference container (typically an object or array)
Passes this container to the target node
The target node deposits its state into the container
The original node can then access this state through the container
This mechanism provides several key advantages:
Performance: No need to copy large state objects
Freshness: Always gets the latest state when accessed
Simplicity: Minimal API surface for powerful functionality
Consistency: Predictable access patterns across different node types
Basic View Usage
// Transform state with views into other nodes' state
orderNode.state.set((state, views) => {
// Access customer data through views
const customerData = views.customer.get(state =>
state.customers[order.customerId]
);
// Access inventory data through views
const inventoryData = views.inventory.get(state =>
state.products[order.productId]
);
// Return updated state that incorporates external data
return {
...state,
orders: {
...state.orders,
[order.id]: {
...order,
canShip: inventoryData.inStock,
shippingAddress: customerData.address
}
}
};
});
Enhanced View Collection
For more efficient collection of state from multiple nodes in a single operation, Happen provides an enhanced collection capability:
orderNode.state.set((state, views) => {
// Collect state from multiple nodes in a single operation
const data = views.collect({
customer: state => ({
name: state.customers[order.customerId].name,
address: state.customers[order.customerId].address
}),
inventory: state => ({
inStock: state.products[order.productId].quantity > 0,
leadTime: state.products[order.productId].leadTime
}),
shipping: state => ({
rates: state.ratesByRegion[customerRegion],
methods: state.availableMethods
})
});
// Use the collected data
return {
...state,
orders: {
...state.orders,
[order.id]: {
...order,
fulfillmentPlan: createFulfillmentPlan(data)
}
}
};
});
This approach:
Traverses the node graph only once
Collects specific slices of state from each relevant node
Transforms the state during collection
Returns a unified object with all the collected data
For more advanced view patterns and usage, see the dedicated State Management page.
The Unified Information System
These three concepts—State, Context, and Views—form a unified information model:
State represents what a node owns and controls
Context represents the causal flow of information through events
Views represent windows into state owned by others
Each serves a distinct purpose in the system, creating a complete information model without redundancy or overlap.
How These Systems Work Together
The power of Happen's information system comes from how these complementary systems work together:
Context + Views
Context and views work together to provide a complete picture of system behavior:
// Both context and views working together
orderNode.on("payment-received", event => {
const { orderId, amount } = event.payload;
const { correlationId } = event.context.causal;
// Update order with view-aware state transformation
orderNode.state.set((state, views) => {
// Get the current order
const currentOrder = state.orders[orderId];
// Collect payment information through enhanced views
const paymentData = views.collect({
payments: state => ({
details: state[event.payload.paymentId],
methods: state.methods
})
});
// Return updated state
return {
...state,
orders: {
...state.orders,
[orderId]: {
...currentOrder,
status: "paid",
paymentId: event.payload.paymentId,
paymentMethod: paymentData.payments.details.method,
transactionId: paymentData.payments.details.transactionId,
correlationId: correlationId, // From context
paymentTimestamp: Date.now()
}
}
};
});
// Emit event with proper context
orderNode.broadcast({
type: "order-paid",
payload: {
orderId,
amount
}
// Context is automatically managed by the framework
});
});
State + Context = Causal State History
The combination of state and context enables rich causal state history:
// State history using context
function getStateHistory(nodeId, statePath) {
// Get all events that affected this state path
const events = eventStore.getEventsForPath(nodeId, statePath);
// Sort by causal order using context information
const sortedEvents = sortByCausalOrder(events);
// Reconstruct state history
let state = undefined;
const history = [];
for (const event of sortedEvents) {
state = applyEvent(state, event);
history.push({
state: { ...state },
event: event.type,
timestamp: event.metadata.timestamp,
causedBy: event.context.causal.causationId,
by: event.context.causal.sender
});
}
return history;
}
State + Views = System-Wide View
The combination of state and views provides a system-wide view:
// System-wide view using state and views
dashboardNode.on("generate-dashboard", event => {
// Use view-aware state transformation to gather system-wide data
dashboardNode.state.set((state, views) => {
// Collect data from various services through enhanced views
const systemData = views.collect({
orders: state => ({
pending: Object.values(state).filter(o => o.status === "pending").length,
completed: Object.values(state).filter(o => o.status === "completed").length,
failed: Object.values(state).filter(o => o.status === "failed").length
}),
inventory: state => ({
lowStock: Object.values(state.products).filter(p => p.stock < 10).length,
outOfStock: Object.values(state.products).filter(p => p.stock === 0).length
}),
customers: state => ({
active: Object.values(state).filter(c => c.active).length,
new: Object.values(state).filter(c => {
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
return c.createdAt >= oneDayAgo;
}).length
}),
payments: state => ({
daily: Object.values(state)
.filter(p => p.timestamp >= Date.now() - (24 * 60 * 60 * 1000))
.reduce((sum, p) => sum + p.amount, 0),
weekly: Object.values(state)
.filter(p => p.timestamp >= Date.now() - (7 * 24 * 60 * 60 * 1000))
.reduce((sum, p) => sum + p.amount, 0)
})
});
// Return updated dashboard state
return {
...state,
dashboard: systemData
};
});
});
Practical Example: Coordinated Operations
Here's a complete example showing how views enable coordinated operations across node boundaries:
// Order fulfillment process using views
fulfillmentNode.on('fulfill-order', (event) => {
const { orderId } = event.payload;
// Update fulfillment state based on views of other nodes
fulfillmentNode.state.set((state, views) => {
// Collect data from multiple services in one operation
const fulfillmentData = views.collect({
orders: state => {
const order = state[orderId];
return order ? { order } : null;
},
inventory: state => ({
products: state.products
}),
customers: state => {
// We need the order first to get the customerId
const order = views.orders.get(s => s[orderId]);
if (!order) return null;
return {
customer: state.customers[order.customerId]
};
},
shipping: state => ({
rates: state.rates
})
});
// Check if order exists
if (!fulfillmentData.orders) {
return state; // No change
}
const order = fulfillmentData.orders.order;
const customer = fulfillmentData.customers?.customer;
if (!customer) {
return state; // No change, customer not found
}
// Check inventory availability
const itemsAvailable = order.items.every(item => {
const product = fulfillmentData.inventory.products[item.productId];
return product && product.stock >= item.quantity;
});
if (!itemsAvailable) {
return state; // No change, items not available
}
// Get shipping option
const shippingOption = fulfillmentData.shipping.rates[customer.region].standard;
// Return updated fulfillment state
return {
...state,
fulfillments: {
...state.fulfillments,
[orderId]: {
orderId,
items: order.items,
customer: {
id: order.customerId,
name: customer.name,
address: customer.address
},
shippingMethod: shippingOption.method,
shippingCost: shippingOption.cost,
estimatedDelivery: calculateDelivery(customer.address, shippingOption),
status: "ready",
createdAt: Date.now()
}
}
};
});
// Check if fulfillment was created
const fulfillment = fulfillmentNode.state.get(state =>
state.fulfillments?.[orderId]
);
if (!fulfillment) {
return {
success: false,
reason: "Could not create fulfillment"
};
}
// Continue to next step
return initiateShipment;
});
The Information System provides a complete model for managing, accessing, and transforming information through three complementary concepts:
State: What nodes own and control
Context: The causal flow of information through events
Views: Windows into state owned by others
Each plays a distinct role, working together to create a powerful yet conceptually clean information model:
State provides ownership and authority
Context provides causality and correlation
Views provide access and visibility
With enhanced view collection capabilities, the Information System enables complex, coordinated operations across node boundaries while maintaining clean separation of concerns and adhering to Happen's philosophy of radical simplicity.
Last updated