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.
1. Install the SDK
Section titled “1. Install the SDK”dotnet add package Hapnd.SdkTypeScript
Section titled “TypeScript”npm install @hapnd/client2. Append Your First Event
Section titled “2. Append Your First Event”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}");TypeScript
Section titled “TypeScript”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}`);3. Add More Events
Section titled “3. Add More Events”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 });TypeScript
Section titled “TypeScript”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 });4. Write a Reducer
Section titled “4. Write a Reducer”A reducer is a C# class that computes state from events. Install the contracts package in a new class library project:
dotnet new classlib -n MyReducerscd MyReducersdotnet add package Hapnd.Projections.ContractsCreate 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);5. Deploy It
Section titled “5. Deploy It”Use the Hapnd CLI to upload, compile, and bind your reducer:
npx @hapnd/cli login sk_live_your_keynpx @hapnd/cli deploynpx @hapnd/cli bind order red_abc123The 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.
6. Query Computed State
Section titled “6. Query Computed State”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}");}TypeScript
Section titled “TypeScript”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}`);}What’s Next
Section titled “What’s Next”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