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:

  1. Unified Access: All nodes use the same node.global API regardless of environment

  2. Native 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

  3. 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:

  1. Creates a shared reference container (typically an object or array)

  2. Passes this container to the target node

  3. The target node deposits its state into the container

  4. 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:

  1. State represents what a node owns and controls

  2. Context represents the causal flow of information through events

  3. 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