npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kolarski/event-master

v1.2.4

Published

Event Master is an Event Sourcing library that abstracts working with events and supports multiple data storage backends (repositories). It provides a flexible and extensible framework for managing events, projections, and event versioning. The library is

Downloads

34

Readme

Event Master

Event Master is an Event Sourcing library that abstracts working with events and supports multiple data storage backends (repositories). It provides a flexible and extensible framework for managing events, projections, and event versioning. The library is designed to be easy to use and integrate into your existing applications.

Documentation

Table of Contents

  1. Introduction
  2. Installation
  3. Setup
  4. Examples
  5. Basic Use-Cases
  6. Advanced Features

Introduction

Event Master is an Event Sourcing library that abstracts working with events and supports multiple data storage backends (repositories). It provides a flexible and extensible framework for managing events, projections, and event versioning. The library is designed to be easy to use and integrate into your existing applications.

Installation

To install Event Master, run the following command:

npm install @kolarski/event-master

Setup

To use Event Master in your project, you need to define your events first. This is done using the Zod library (https://zod.dev/). That way not only you are getting an a TypeScript type safty but also you are getting, runtime safety as well, thanks to Zod's parsers. Zod is already included in the project, no need to install it seperatly. You just need to extend the baseEvent already defined in the project with your custom payload

import { EM, baseEvent, z } from "@kolarski/event-master";

// Define your event schema
const pageVisitedEvent = baseEvent
  .extend({
    type: z.literal("page-visited"),
    payload: z.object({
      url: z.string().url(),
      visitedDate: z.string().datetime(),
    }),
  })
  .readonly();

const pageMissingEvent = baseEvent
  .extend({
    type: z.literal("page-missing"),
    payload: z.object({
      url: z.string().url(),
      visitedDate: z.string().datetime(),
    }),
  })
  .readonly();

const eventSchema = z.union([pageVisitedEvent, pageMissingEvent]);

// Create an instance of EM with the required configurations
const em = EM.create({
  events: eventSchema,
});

Examples

Defining an Event

Define a custom event using Zod and the base event schema.

import { baseEvent, z } from "@kolarski/event-master";

const pageVisitedEvent = baseEvent
  .extend({
    type: z.literal("page-visited"),
    payload: z.object({
      url: z.string().url(),
      visitedDate: z.string().datetime(),
    }),
  })
  .readonly();

export { pageVisitedEvent };

Emitting Events

Emit events using the EM instance.

const event = {
  type: "page-visited",
  entityId: "page-1",
  payload: {
    url: "https://example.com",
    visitedDate: new Date().toISOString(),
  },
};

em.emit(event).then(() => console.log("Event emitted"));

Replaying Events

Replay events to reconstruct application state.

async function replayEvents() {
  for await (const event of em.replay({ entityId: "page-1" })) {
    console.log(`Replayed event: ${event.type} at ${event.seq}`);
  }
}

replayEvents();

Basic Use-Cases

Custom Repositories

You can use custom repositories by implementing the Repository interface and passing an instance to the EM configuration.

import { EM } from "@kolarski/event-master";
import type { Repository } from "@kolarski/event-master";
import type { BaseEventType } from "@kolarski/event-master";

class CustomRepository<Event extends BaseEventType>
  implements Repository<Event>
{
  private events: Array<Readonly<Event>> = [];
  private currentSeq: number = 0;

  async emitEvent(event: Event): Promise<void> {
    event.seq = ++this.currentSeq;
    this.events.push(event);
  }

  async *replay(query: ReplayQuery<Event>): AsyncIterable<Event> {
    for (const event of this.events) {
      if (query.entityId && event.entityId !== query.entityId) continue;
      if (query.seq && query.seq.from && event.seq < query.seq.from) continue;
      if (query.seq && query.seq.to && event.seq > query.seq.to) continue;
      yield event;
    }
  }

  async getAllEvents(): Promise<Array<Event>> {
    return this.events;
  }

  async getLastProcessedEventId(
    projectionName: string
  ): Promise<string | null> {
    return null;
  }
}

// Use the custom repository in the EM instance
const em = EM.create({
  events: eventSchema,
  repository: new CustomRepository(),
});

Custom Loggers

Custom loggers can be integrated by implementing the Logger interface and passing an instance to the EM configuration.

import { EM } from "@kolarski/event-master";
import type { Logger, BaseEventType } from "@kolarski/event-master";

class CustomLogger<Event extends BaseEventType> implements Logger<Event> {
  async logEvent(event: Event): Promise<void> {
    console.log(`Custom log: Event emitted - ${event.type}`);
  }
}

// Use the custom logger in the EM instance
const em = EM.create({
  events: eventSchema,
  logger: new CustomLogger(),
});

Event Upgraders

Event upgraders help in handling changes in event structure over time. Implement the EventUpgrader interface and pass an array of upgraders to the EM configuration.

import { EM } from "@kolarski/event-master";
import type { EventUpgrader } from "@kolarski/event-master";
import { z } from "zod";

const PageVisitedEvent = z.object({
  type: z.literal("page-visited"),
  version: z.literal(1),
  entityId: z.string(),
  payload: z.object({
    url: z.string().url(),
    visitedDate: z.string().datetime(),
  }),
});

const PageVisitedV2Event = z.object({
  type: z.literal("page-visited"),
  version: z.literal(2),
  entityId: z.string(),
  payload: z.object({
    url: z.string().url(),
    visitedDate: z.string().datetime(),
    userAgent: z.string().default("unknown"),
  }),
});

class PageVisitedEventUpgrader
  implements EventUpgrader<typeof PageVisitedEvent>
{
  upgrade(event: typeof PageVisitedEvent): typeof PageVisitedV2Event {
    if (event.version === 1) {
      return {
        ...event,
        version: 2,
        payload: {
          ...event.payload,
          userAgent: "unknown",
        },
      };
    }
    return event;
  }

  downgrade(event: typeof PageVisitedV2Event): typeof PageVisitedEvent {
    if (event.version === 2) {
      const { userAgent, ...restPayload } = event.payload;
      return {
        ...event,
        version: 1,
        payload: restPayload,
      };
    }
    return event;
  }
}

// Use the event upgrader in the EM instance
const em = EM.create({
  events: eventSchema,
  upgraders: [new PageVisitedEventUpgrader()],
});

If you create something useful, please consider contributing it to the project by submitting a pull request. Your contributions are welcome and help the community grow!

Advanced Features

Handling Multiple Event Types

Event Master supports handling multiple event types by defining a union of event schemas.

import { EM, baseEvent, z } from "@kolarski/event-master";

const pageVisitedEvent = baseEvent
  .extend({
    type: z.literal("page-visited"),
    payload: z.object({
      url: z.string().url(),
      visitedDate: z.string().datetime(),
    }),
  })
  .readonly();

const userRegisteredEvent = baseEvent
  .extend({
    type: z.literal("user-registered"),
    payload: z.object({
      userId: z.string().uuid(),
      registeredAt: z.string().datetime(),
    }),
  })
  .readonly();

const eventSchema = z.union([pageVisitedEvent, userRegisteredEvent]);

const em = EM.create({
  events: eventSchema,
});

Event Projections

Event projections allow you to create derived data by replaying events.

async function projectEvents() {
  const projection = {};

  for await (const event of em.replay({ entityId: "page-1" })) {
    // Update the projection based on the event
    if (event.type === "page-visited") {
      projection[event.entityId] = projection[event.entityId] || [];
      projection[event.entityId].push(event.payload);
    }
  }

  console.log(projection);
}

projectEvents();