@storevent/storevent
TypeScript icon, indicating that this package has built-in type declarations

1.0.0 • Public • Published

Storevent

Storevent is a framework that simplify event sourcing. It makes it easy to build an entity reducer to aggregate your events into a state.

This package @storevent/stovevent provides interfaces that you can use to build custom implementation for your event store.

You can also decide to use a packages that provides an implementation for Postgres, MongoDB, etc.... See Available Packages List

Documentation

📕 Types Documentation Here 📕

Examples

Storevent provides examples that takes the case of an entity Account that represents a simple bank account. It can be created, credited with some money or debited. Each of these action produce an event: AccountCreated, AccountCredited, AccountDebited.

See Account Example Here

Example shows:

  • How to create your:

    • Entity Events
    • Entity State
    • Entity Reducer that will calculate your entity state from your events.
  • How to use the In Memory implementation:

    • event store
    • snaphshot store
    • hybrid store
  • How to use the Postgres implementation:

    • event store
    • snaphshot store
    • hybrid store

Event

To create your events you just need to extends the BasicEvent interface.

import { BasicEvent } from "@storevent/storevent";

type AccountEvent = AccountCreated | AccountCredited | AccountDebited;

interface AccountCreated extends BasicEvent {
  name: "AccountCreated";
  payload: {
    accountId: string;
    holderName: string;
    status: "OPEN";
    balance: number;
    currency: string;
  };
}

interface AccountCredited extends BasicEvent { ... }
interface AccountDebited extends BasicEvent { ... }

Entity State

Your entity state just need to extends JsonSerializable type.

import { JsonSerializable } from "@storevent/storevent";

interface AccountState extends JsonSerializable {
  accountId: string;
  holderName: string;
  status: "CLOSED" | "OPEN";
  balance: number;
  currency: string;
}

Entity Reducer

The entity reducer is the component that calculates the state of your entity. For this it takes an initial state and a list of event to apply on top of this state.

To create an entity reducer you just need to extends the EntityReducer class. Then mount your event reducers using the mountEventReducer method.

Reducer Declaration

import { AccountEvent, AccountState } from "../AccountEntity";

class AccountReducer extends EntityReducer<AccountState, AccountEvent> {
  constructor() {
    super("Account");

    this.mountEventReducer("AccountCreated", applyAccountCreatedEvent);
    this.mountEventReducer("AccountCredited", applyAccountCreditedEvent);
    this.mountEventReducer("AccountDebited", applyAccountDebitedEvent);
  }
}

function applyAccountCreditedEvent(params: {
  state: AccountState;
  event: AccountCredited;
}): AccountState {
  const { state, event } = params;

  return {
    holderName: state.holderName,
    accountId: state.accountId,
    balance: state.balance + event.payload.amount,
    currency: state.currency,
    status: state.status,
  };
}

function applyAccountDebitedEvent() { ... }

function applyAccountCreatedEvent() { ... }

Reducer Usage

const initialState = { ... }

const events: AccountEvent[] = [ accountCreated, accountCredited, accountDebited, ...]

const newState = new AccountReducer().reduceEvents({
  state: initialState,
  stateVersion: 0,
  events,
});

Event Store Interface

The event store interface provides an interface to append new events in your event store and a method to retrieve your events. You can also subscribe to notifications to trigger a callback when new events are appended to the store.

const accountEventStore = new AccountInMemoryEventStore()

// Appending events
await accountEventStore.append({
  entityId: accountId,
  events: [accountCreatedEvent],
});

// Retrieve all events
const events = await accountEventStore.getEventsFromOffset({
  entityId: accountId,
});

// Retrieve event from a given offset
const events = await accountEventStore.getEventsFromOffset({
  entityId: accountId,
  offset: 45, // optional default to 0
});

// Register a listener to be notified when new events are appended.
accountEventStore.onEventAppended((notification) => {
  notification.entityName;
  notification.entityId;
  notification.events;
});

Advanced Event Store Interface

The AdvanceEventStore interface is here to allow saving your entity events and also persist a snapshot in a transactionnal way. It provide the same methods as EventStore. The only difference is the appendWithSnapshot method that take a snaphost and an array of events.

const snapshotToSave = {
  state: { content: 'My entity state' },
  version: 349
}

await myAdvanceEventStore.appendWithSnapshot({
  entityId,
  events: [eventA, eventB],
  snapshot: snapshotToSave,
  appendAfterOffset: 347
});

// Register a listener to be notified when new events are appended.
myAdvanceEventStore.onEventAppended((notification) => {
  notification.entityName;
  notification.entityId;
  notification.events;
});

You can check this for more details Account Example Here

Implement a custom store

You can implement your own event, snapshot and hybrid store. For this just implement @storevent/storevent interfaces. You are of course free to enrich those interfaces with some specific methods related to your project.

See examples section to see how in memory, postgres and mongodb implementation are made.

Available implementations

Storevent Error

Storevent implementations will always try to throw a StoreventError.

Properties

A StoreventError has the following properties:

  • name: equals to StoreventError.

  • code: Unique storevent error code.

  • details: Generic type that contains the error context. You can discriminate the type with the error.code.

  • cause: Original error object if the error is wrapped. Mostly used when the error comes from the underlying layer (postgres, mongo, etc...)

Specific errors

  • ConcurrencyError: Use this error in your implementation to prevent events from being appended concurrently for the same entity.
  • WrongOffsetError: Use this error in your implementation when you detect inconsistency in your event offset.
  • UnknownReducerError: This error is thrown when an EntityReducer cannot find a reducer for a given event name.

Package Sidebar

Install

npm i @storevent/storevent

Weekly Downloads

0

Version

1.0.0

License

MIT

Unpacked Size

35.8 kB

Total Files

35

Last publish

Collaborators

  • sasquatch