![site/logo.svg](site/logo.svg) # Event Emitting and Middleware Hooks [![tests](https://github.com/jaredwray/hookified/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/hookified/actions/workflows/tests.yaml) [![GitHub license](https://img.shields.io/github/license/jaredwray/hookified)](https://github.com/jaredwray/hookified/blob/master/LICENSE) [![codecov](https://codecov.io/gh/jaredwray/hookified/graph/badge.svg?token=nKkVklTFdA)](https://codecov.io/gh/jaredwray/hookified) [![npm](https://img.shields.io/npm/dm/hookified)](https://npmjs.com/package/hookified) [![jsDelivr hits](https://img.shields.io/jsdelivr/npm/hm/hookified)](https://www.jsdelivr.com/package/npm/hookified) [![npm](https://img.shields.io/npm/v/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 - Maintained on a regular basis! # 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 ``` if you are not using ESM modules, you can use the following: ```html ``` # 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 ``` ## .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 warning console.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). # License [MIT & © Jared Wray](LICENSE)