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

event-closet

v0.2.0

Published

a nice place to store and organize your events

Downloads

2

Readme

event-closet

installation

npm install event-closet

example

import EventCloset from 'event-closet';

const closet = EventCloset(); // default options (uses in-memory storage, not suitable for production)

// add an aggregate
const decisionProjection = (state = { created: false }, event) => {
  if (event.type === 'created') {
    return { created: true };
  }
  return state;
};
closet.registerAggregate('user', decisionProjection);

// add a read projection
const nbUsersProjection = (state = 0, event) => {
  if (event.type === 'created') {
    return state + 1;
  }
  return state;
};
closet.registerProjection('nb-users', ['user'], nbUsersProjection);

// add a command
const createUser = (projection, { name }) => {
  if (projection.created) {
    throw new Error('user already created');
  }
  // return the new event to store
  // (mandatory fields like 'aggregate' and 'id' will be automatically set)
  return { type: 'created', name };
};
closet.registerCommand('user', 'create', createUser);

// a command is received!
await closet.handleCommand('user', 'user123', 'create', { name: 'John Doe' });

// get projection current state
const nbUsers = await closet.getProjection('nb-users'); // returns 1

Or with the promises syntax instead of async/await:

closet.handleCommand('user', 'user123', { name: 'John Doe' })
.then(() => closet.getProjection('nb-users'))
.then((nbUsers) => {
  // nbUsers === 1
});

What are events?

Events are plain javascript object. They have some special fields and any number of custom fields.

const event = {
  // special mandatory fields
  aggregate: 'user',
  id: 'user123',
  sequence: 1,
  insertDate: new Date().toISOString(),
  // user-defined fields
  type: 'created',
  version: 1,
  name: 'John Doe',
}

options

const closet = EventCloset({ /* your options here */ });

storage (default: inMemoryStorage())

See below to read how storages work.

snapshotEvery (default: null)

If it's a number, the closet will save snapshots of the entities projections every N events in order to avoid having to replay all the events stack to get the projections state.

If it's null, no snapshot will be taken.

logger (default: no logging)

An object that has info and error methods to let the closet write logs.

api

registerAggregate

Call this function to add an aggregate to your closet.

const name = 'user';
const decisionProjection = (state, event) => { /* something */ return newState; };
const options = { snapshotEvery: 50 };
closet.registerAggregate(name, decisionProjection, options);
  • decisionProjection is the reducer function that will be used to generate the projection in handleCommand.
  • options can contain the following values:
    • snapshotEvery to override the global snapshotEvery option for this aggregate.

registerEntityProjection

Call this function to add a projection that we can later apply on a single entity of the aggregate with getEntityProjection.

const aggregate = 'user';
const name = 'identity';
const projection = (state, event) => { /* something */ return newState; }
const options = { snapshotEvery: 50 };
closet.registerEntityProjection(aggregate, name, projection, options);
  • projection is the reducer function that will be used to generate the projection in getEntityProjection.
  • options can contain the following values:
    • snapshotEvery to override the global snapshotEvery option for this projection.

registerCommand

Call this function to add a command handler: something that will receive the current decision projection of the entity and some context data and will return the new event(s) to store.

const aggregate = 'user';
const name = 'create';
const commandHandler = (projection, params) => { /* something */ return newEvents }
closet.registerCommand(aggregate, name, commandHandler);
  • commandHandler is a function that is given the decision projection + the command params and that return a new event to store, or an array of new events.

    The produced events are plain javascript objects (see "What is an event" above). The special fields will be filled in by the closet, only user fields must be present in the object.

handleCommand

Call this function when a command is actually received.

const aggregate = 'user';
const id = 'user123';
const command = 'create';
const params = { /* command params */ };
await closet.handleCommand(aggregate, id, command, params);

Return value: the event(s) returned by the handler.

getEntityProjection

Call this function to get a projection of a single entity of your closet.

const aggregate = 'user';
const id = 'user123';
const projection = 'identity';
const identity = await closet.getEntityProjection(aggregate, id, projection);

Return value: the result of the projection applied to all the entity's events.

registerProjection

Call this function to add a new projection to your closet.

const name = 'nb-users';
const onAggregates = ['user'];
const projection = (state, event) => { /* something */ return newState; };
const options = {
  onChange: (state, event) => { /* do something */ }
};
closet.registerProjection(name, onAggregates, projection, options);
  • onAggregates is an array of the aggregates we will listen to.
  • projection is the reducer function that we will apply to the events.
  • options can contain the following values:
    • onChange, a handler that will be called each time the projection changes with 2 arguments: the new state and the event.

getProjection

Call this function to get the current state of a projection.

const nbUsers = await closet.getProjection('nb-users');

Return value: the result of the projection applied to all the events.

onEvent

Call this function to register a listener to all new events.

closet.onEvent((event) => { /* do something */ });

rebuild

Projections and snapshots are persisted in the underlying storage. Call this function to rebuild them from the first event.

await closet.rebuild();

storage

inMemoryStorage

Everything is stored in memory, everything is lost when the app exits.

import EventCloset, { inMemoryStorage } from 'event-closet';

const closet = EventCloset({ storage: inMemoryStorage() });

mongoStorage

There are 2 ways of using it:

  • give the mongo url (and optionaly the connect options), call connect.
import EventCloset, { mongoStorage } from 'event-closet';

const storage = mongoStorage({
  url: 'mongodb://localhost:27017/eventCloset',
  connectOptions: {},
});
await storage.connect();
const closet = EventCloset({ storage });
/* ... */
storage.close(); // to end connection
  • give it an already connected db object.
import EventCloset, { mongoStorage } from 'event-closet';
import { MongoClient } from 'mongodb';

const db = await MongoClient.connect(url, options);
const closet = EventCloset({
  storage: mongoStorage({ db });
});

Other options available:

const storage = mongostorage({
  eventsCollection: 'events', // name of the mongo events collection
  projectionsCollection: 'projections', // name of the mongo projections collection
});

Custom storage

To support any other type of storage, you can pass a custom storage object (async function are expected to return promises):

import EventCloset from 'event-closet';

const storage = {
  storeEvent: async (event) => {
    // store a new event
  },
  getEvents: (aggregate, id, fromSequence) => {
    // return a Readable Stream of events sorted by sequence
    // if fromSequence is provided, return only events of higher sequence
  },
  getAllEvents: () => {
    // return a Readable Stream of events sorted by insertDate then sequence
  },
  storeProjection: async (name, state) => {
    // store a read projection
  },
  getProjection: async (name) => {
    // get a read projection
  },
  storeSnapshot: async (aggregate, id, projection, snapshot) => {
    // store a snapshot
  },
  getSnapshot: async (aggregate, id, projection) => {
    // get a snapshot
  },
};
const closet = EventCloset({ storage });