https://github.com/jaredwray/hookified
Event Emitting and Async Middleware Hooks 🪝
https://github.com/jaredwray/hookified
async-hooks emit emittery event-driven eventemitter eventemitter3 hooks middleware
Last synced: 7 months ago
JSON representation
Event Emitting and Async Middleware Hooks 🪝
- Host: GitHub
- URL: https://github.com/jaredwray/hookified
- Owner: jaredwray
- License: mit
- Created: 2024-09-02T18:30:39.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-13T15:27:45.000Z (7 months ago)
- Last Synced: 2025-03-13T15:33:54.958Z (7 months ago)
- Topics: async-hooks, emit, emittery, event-driven, eventemitter, eventemitter3, hooks, middleware
- Language: TypeScript
- Homepage: https://hookified.org
- Size: 331 KB
- Stars: 2
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README

# Event Emitting and Middleware Hooks
[](https://github.com/jaredwray/hookified/actions/workflows/tests.yaml)
[](https://github.com/jaredwray/hookified/blob/master/LICENSE)
[](https://codecov.io/gh/jaredwray/hookified)
[](https://npmjs.com/package/hookified)
[](https://www.jsdelivr.com/package/npm/hookified)
[](https://npmjs.com/package/hookified)# Features
- Simple replacement for EventEmitter
- Async / Sync Middleware Hooks for Your Methods
- ESM / CJS with Types and Nodejs 20+
- Browser Support and Delivered via CDN
- Ability to throw errors in hooks
- Ability to pass in a logger (such as Pino) for errors
- No package dependencies and only 100KB in size
- Fast and Efficient with [Benchmarks](#benchmarks)
- Maintained on a regular basis!# Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Using it in the Browser](#using-it-in-the-browser)
- [API - Hooks](#api---hooks)
- [.throwHookErrors](#throwhookerrors)
- [.onHook(eventName, handler)](#onhookeventname-handler)
- [.onceHook(eventName, handler)](#oncehookeventname-handler)
- [.prependHook(eventName, handler)](#prependhookeventname-handler)
- [.prependOnceHook(eventName, handler)](#prependoncehookeventname-handler)
- [.removeHook(eventName)](#removehookeventname)
- [.hook(eventName, ...args)](#hookeventname-args)
- [.hooks](#hooks)
- [.getHooks(eventName)](#gethookseventname)
- [.clearHooks(eventName)](#clearhookeventname)
- [API - Events](#api---events)
- [.on(eventName, handler)](#oneventname-handler)
- [.off(eventName, handler)](#offeventname-handler)
- [.emit(eventName, ...args)](#emiteventname-args)
- [.listeners(eventName)](#listenerseventname)
- [.removeAllListeners(eventName)](#removealllistenerseventname)
- [.setMaxListeners(maxListeners: number)](#setmaxlistenersmaxlisteners-number)
- [.once(eventName, handler)](#oneventname-handler-1)
- [.prependListener(eventName, handler)](#prependlistenereventname-handler)
- [.prependOnceListener(eventName, handler)](#prependoncelistenereventname-handler)
- [.eventNames()](#eventnames)
- [.listenerCount(eventName?)](#listenercounteventname)
- [.rawListeners(eventName?)](#rawlistenerseventname)
- [Development and Testing](#development-and-testing)
- [Benchmarks](#benchmarks)
- [License](#license)# Installation
```bash
npm install hookified --save
```# Usage
This was built because we constantly wanted hooks and events extended on libraires we are building such as [Keyv](https://keyv.org) and [Cacheable](https://cacheable.org). This is a simple way to add hooks and events to your classes.```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
}//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}
```You can even pass in multiple arguments to the hooks:
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
let data2 = { some: 'data2' };
// do something
await this.hook('before:myMethod2', data, data2);return data;
}
}
```# Using it in the Browser
```html
import { Hookified } from 'https://cdn.jsdelivr.net/npm/hookified/dist/browser/index.js';
class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
}//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}```
if you are not using ESM modules, you can use the following:
```html
class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
}//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}```
# API - Hooks
## .throwHookErrors
If set to true, errors thrown in hooks will be thrown. If set to false, errors will be only emitted.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super({ throwHookErrors: true });
}
}const myClass = new MyClass();
console.log(myClass.throwHookErrors); // true. because it is set in super
try {
myClass.onHook('error-event', async () => {
throw new Error('error');
});await myClass.hook('error-event');
} catch (error) {
console.log(error.message); // error
}myClass.throwHookErrors = false;
console.log(myClass.throwHookErrors); // false
```## .logger
If set, errors thrown in hooks will be logged to the logger. If not set, errors will be only emitted.```javascript
import { Hookified } from 'hookified';
import pino from 'pino';const logger = pino(); // create a logger instance that is compatible with Logger type
class MyClass extends Hookified {
constructor() {
super({ logger });
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async () => {
throw new Error('error');
});// when you call before:myMethod2 it will log the error to the logger
await myClass.hook('before:myMethod2');
```## .onHook(eventName, handler)
Subscribe to a hook event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
```## .onceHook(eventName, handler)
Subscribe to a hook event once.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHookOnce('before:myMethod2', async (data) => {
data.some = 'new data';
});myClass.myMethodWithHooks();
console.log(myClass.hooks.length); // 0
```## .prependHook(eventName, handler)
Subscribe to a hook event before all other hooks.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
myClass.preHook('before:myMethod2', async (data) => {
data.some = 'will run before new data';
});
```## .prependOnceHook(eventName, handler)
Subscribe to a hook event before all other hooks. After it is used once it will be removed.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
myClass.preHook('before:myMethod2', async (data) => {
data.some = 'will run before new data';
});
```## .removeHook(eventName)
Unsubscribe from a hook event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
const handler = async (data) => {
data.some = 'new data';
};myClass.onHook('before:myMethod2', handler);
myClass.removeHook('before:myMethod2', handler);
```## .hook(eventName, ...args)
Run a hook event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}
```in this example we are passing multiple arguments to the hook:
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
let data2 = { some: 'data2' };
// do something
await this.hook('before:myMethod2', data, data2);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data, data2) => {
data.some = 'new data';
data2.some = 'new data2';
});await myClass.myMethodWithHooks();
```## .hooks
Get all hooks.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});console.log(myClass.hooks);
```## .getHooks(eventName)
Get all hooks for an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});console.log(myClass.getHooks('before:myMethod2'));
```## .clearHooks(eventName)
Clear all hooks for an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodWithHooks() Promise {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);return data;
}
}const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});myClass.clearHooks('before:myMethod2');
```# API - Events
## .on(eventName, handler)
Subscribe to an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});
```## .off(eventName, handler)
Unsubscribe from an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});myClass.off('message', (message) => {
console.log(message);
});
```## .emit(eventName, ...args)
Emit an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
```## .listeners(eventName)
Get all listeners for an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});console.log(myClass.listeners('message'));
```## .removeAllListeners(eventName)
Remove all listeners for an event.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});myClass.removeAllListeners('message');
```## .setMaxListeners(maxListeners: number)
Set the maximum number of listeners and will truncate if there are already too many.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}const myClass = new MyClass();
myClass.setMaxListeners(1);
myClass.on('message', (message) => {
console.log(message);
});myClass.on('message', (message) => {
console.log(message);
}); // this will not be added and console warningconsole.log(myClass.listenerCount('message')); // 1
```## .once(eventName, handler)
Subscribe to an event once.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.once('message', (message) => {
console.log(message);
});myClass.emit('message', 'Hello World');
myClass.emit('message', 'Hello World'); // this will not be called
```## .prependListener(eventName, handler)
Prepend a listener to an event. This will be called before any other listeners.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.prependListener('message', (message) => {
console.log(message);
});
```## .prependOnceListener(eventName, handler)
Prepend a listener to an event once. This will be called before any other listeners.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.prependOnceListener('message', (message) => {
console.log(message);
});myClass.emit('message', 'Hello World');
```## .eventNames()
Get all event names.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});console.log(myClass.eventNames());
```## .listenerCount(eventName?)
Get the count of listeners for an event or all events if evenName not provided.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});console.log(myClass.listenerCount('message')); // 1
```## .rawListeners(eventName?)
Get all listeners for an event or all events if evenName not provided.
```javascript
import { Hookified } from 'hookified';class MyClass extends Hookified {
constructor() {
super();
}
}const myClass = new MyClass();
myClass.on('message', (message) => {
console.log(message);
});console.log(myClass.rawListeners('message'));
```# Development and Testing
Hookified is written in TypeScript and tests are written in `vitest`. To run the tests, use the following command:
To setup the environment and run the tests:
```bash
npm i && npm test
```To contribute follow the [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
# Benchmarks
We are doing very simple benchmarking to see how this compares to other libraries using `tinybench`. This is not a full benchmark but just a simple way to see how it performs. Our goal is to be as close or better than the other libraries including native (EventEmitter).
## Hooks
| name | summary | ops/sec | time/op | margin | samples |
|-------------------|:---------:|----------:|----------:|:--------:|----------:|
| Hookified 1.8.0 | 🥇 | 4M | 299ns | ±2.42% | 3M |
| Hookable 5.5.3 | -73% | 982K | 1µs | ±2.92% | 812K |## Emits
This shows how close the native `EventEmitter` is to `hookified` and `eventemitter3`. We are using the same test as above but just emitting events. It is not a fair comparison but it is interesting to see how close they are.
| name | summary | ops/sec | time/op | margin | samples |
|-------------------------|:---------:|----------:|----------:|:--------:|----------:|
| Hookified 1.8.0 | 🥇 | 10M | 112ns | ±1.13% | 9M |
| EventEmitter3 5.0.1 | -1.3% | 10M | 114ns | ±1.84% | 9M |
| EventEmitter v22.12.0 | -1.5% | 9M | 114ns | ±1.18% | 9M |
| Emittery 1.1.0 | -92% | 785K | 1µs | ±0.45% | 761K |_Note: the `EventEmitter` version is Nodejs versioning._
# License
[MIT & © Jared Wray](LICENSE)