handlery
TypeScript icon, indicating that this package has built-in type declarations

0.2.7 • Public • Published

handlery logo

handlery: typed event handling like it should be.

build status docs deployment GitHub deployments handlery on npm


handlery (like emittery but for 'handle') is a super clean and easy way to handle any kind of events with class-based handlers (and decorators!). It works with all kinds of event emitters (emittery, nodejs' EventEmitter...) out of the box! Just look at it:

@register()
class UserHandler extends EventHandler {
  @on('user.add')
  public handleAddUser(user: Events['user.add'], ctx: HandlerContext) {
    ctx.emitter.emit('user.add.response', { ... })
    // ...
  }
}

It requires typescript decorators support (modern, ECMA decorators. Not Decorators.Legacy etc.) And an event emitter like emittery or node's built-in EventEmitter.

Installation

It's on npm!

npm install handlery
# or
pnpm add handlery
# or
yarn add handlery

Usage

(Check out our docs)

First, you need some event emitter. handlery works with emittery, the nodejs event emitter and many more. Let's use emittery as an example.

First, define some events. Event IDs can be strings, numbers or symbols

export type AppEvents = {
  'user.add': { name: string };
  'user.remove': { id: string };
};

const EMITTERY = new Emittery<AppEvents>();

then, import the right adapter from handlery/adapters, in this case emitteryAdapter and pass your emittery instance. Pass the converted emitter to handlery

const emitter = emitteryAdapter(EMITTERY);
const { on, subscribe, EventHandler } = handlery(emitter);

The returned Handlery type has the functions on, subscribe and a class EventHandler that can be used to create your handler classes:

@subscribe()
export class UserHandler extends EventHandler {
  @on('user.add')
  public handleAddUser(data: AppEvents['user.add']) {
    console.log('User created:', data.name);
  }

  @on('user.remove')
  public handleRemoveUser(data: AppEvents['user.remove']) {
    console.log('User removed with ID:', data.id);
  }
}

The listener functions are created right away, and start listening instantly due to the subscribe decorator on the class. If you leave that subscribe decorator away, you can always register and subscribe the class later with

UserHandler.register();
// or
UserHandler.subscribe();

The difference between those is that register creates an instance of the class and subscribe actually starts listening to events. If you use the @subscribe() decorator, the handler class will be registered and subscribed right away. Of course, you can also call UserHandler.unsubscribe() later on.

Different emitter adapters

The emitteryHandler function I showed you above is a wrapper around the emittery adapter, which can be imported from handlery/adapters. You could write the same thing as

import handlery from 'handlery';
import { emitteryAdapter } from 'handlery/adapters';

const EMITTERY = new Emittery<AppEvents>();

const emitter = emitteryAdapter(EMITTERY);
const { on, EventHandler } = handlery(emitter);

The purpose of the adapters is to take any form of event emitter and turn it into a simpler, handlery-compatible version. This is done to support all kinds of emitter typings, and different function signatures. For example, emittery's on method can take an abort signal as part of an options parameter, returns an unsubscribe function and can be async. The nodejs EventEmitter on only has two arguments, returns the EventEmitter and is always synchronous.

To ensure this library is 'emitter-agnostic', we ensure a single, easy-to-use format.

The node EventEmitter adapter

Node's EventEmitter is special in that it passes multiple parameters to it's handlers/emitters. Where in emittery you would do something like

emitter.emit('myEvent', { myData1: 42, myDataTwo: 'test' });

In node, you would pass the data as an argument list. Because of that, handlers receive arguments as an array!

class MyHandler extends EventHandler {
  @on('myEvent')
  public handleTestEvent2(data: [number, string]) {
    console.log('Handled myEvent:', data);
  }
}

The good thing is that handlery is able to infer all types from the EventEmitter. So if you typed that, you're good!

type Events = {
  testEvent1: [string];
  testEvent2: [number, string];
};

const eventEmitter = new EventEmitter<Events>();
const adapter = nodeAdapter(eventEmitter);
const { on, EventHandler } = handlery(adapter);

// `on` and `EventHandler` know that the `data` prop is of type `[string]` or `[string, number]`.
class TestHandler extends EventHandler {
  @on('testEvent1')
  public handleTestEvent1(data: [string]) {
    console.log('Handled testEvent1:', data);
  }

  @on('testEvent2')
  public handleTestEvent2(data: [number, string]) {
    console.log('Handled testEvent2:', data);
  }
}

Package Sidebar

Install

npm i handlery

Weekly Downloads

287

Version

0.2.7

License

MIT

Unpacked Size

55.3 kB

Total Files

35

Last publish

Collaborators

  • carnageous