Skip to content

Quick Start

This guide takes you from zero to appending events and querying computed state. By the end, you’ll have events flowing, a reducer computing state, and the ability to query that state — all without provisioning any infrastructure.

Terminal window
dotnet add package Hapnd.Sdk
Terminal window
npm install @hapnd/client
using Hapnd.Client;
var hapnd = new HapndClient("hpnd_your_api_key");
var result = await hapnd.Aggregate("order_001")
.Append(new OrderCreated { CustomerId = "customer_123" });
Console.WriteLine($"Event {result.EventId} at version {result.Version}");
import { createHapndClient } from "@hapnd/client";
const hapnd = createHapndClient("hpnd_your_api_key");
const result = await hapnd
.aggregate("order_001")
.append("OrderCreated", { customerId: "customer_123" });
console.log(`Event ${result.eventId} at version ${result.version}`);

Events build on each other. Use ExpectVersion for optimistic concurrency.

await hapnd.Aggregate("order_001")
.ExpectVersion(1)
.Append(new ItemAdded { Item = "Widget", Price = 25.00m });
await hapnd.Aggregate("order_001")
.ExpectVersion(2)
.Append(new ItemAdded { Item = "Gadget", Price = 15.00m });
await hapnd
.aggregate("order_001")
.expectVersion(1)
.append("ItemAdded", { item: "Widget", price: 25.0 });
await hapnd
.aggregate("order_001")
.expectVersion(2)
.append("ItemAdded", { item: "Gadget", price: 15.0 });

A reducer is a C# class that computes state from events. Install the contracts package in a new class library project:

Terminal window
dotnet new classlib -n MyReducers
cd MyReducers
dotnet add package Hapnd.Projections.Contracts

Create OrderReducer.cs:

using Hapnd.Projections.Contracts;
public record OrderState
{
public decimal Total { get; init; }
public List<string> Items { get; init; } = [];
public int ItemCount { get; init; }
public string? CustomerId { get; init; }
}
public class OrderReducer : IReducer<OrderState>
{
public OrderState Apply(OrderState? state, Event @event)
{
state ??= new OrderState();
return @event.Type switch
{
"OrderCreated" => state with
{
CustomerId = @event.GetData<OrderCreatedData>().CustomerId
},
"ItemAdded" => ApplyItemAdded(state, @event),
_ => state
};
}
private static OrderState ApplyItemAdded(OrderState state, Event @event)
{
var data = @event.GetData<ItemAddedData>();
return state with
{
Total = state.Total + data.Price,
Items = [..state.Items, data.Item],
ItemCount = state.ItemCount + 1
};
}
}
public record OrderCreatedData(string? CustomerId);
public record ItemAddedData(string Item, decimal Price);

Use the Hapnd CLI to upload, compile, and bind your reducer:

Terminal window
npx @hapnd/cli login sk_live_your_key
npx @hapnd/cli deploy
npx @hapnd/cli bind order red_abc123

The CLI auto-detects your project, zips the source files, uploads them, and Hapnd compiles server-side using Roslyn. The bind command tells Hapnd to run this reducer every time an event is appended to an order aggregate.

Now every event append returns computed state, and you can query it directly.

var aggregate = await hapnd.Aggregate("order_001").GetState<OrderState>();
if (aggregate is not null)
{
Console.WriteLine($"Version: {aggregate.Version}");
Console.WriteLine($"Customer: {aggregate.State.CustomerId}");
Console.WriteLine($"Total: {aggregate.State.Total}");
Console.WriteLine($"Items: {aggregate.State.ItemCount}");
}
interface OrderState {
total: number;
items: string[];
itemCount: number;
customerId: string | null;
}
const aggregate = await hapnd.aggregate("order_001").getState<OrderState>();
if (aggregate) {
console.log(`Version: ${aggregate.version}`);
console.log(`Customer: ${aggregate.state.customerId}`);
console.log(`Total: ${aggregate.state.total}`);
console.log(`Items: ${aggregate.state.itemCount}`);
}

You now have the fundamentals working. From here:

  • Events — learn about event types, batch appends, and distributed tracing
  • Reducers — go deeper on reducer patterns, multi-reducer DLLs, and security constraints
  • Projections — build asynchronous read models across aggregates
  • Notifications — get real-time updates via webhooks, WebSocket, or polling
  • .NET SDK — full SDK reference including subscriptions and DI integration
  • TypeScript SDK — full SDK reference for JavaScript runtimes