{"id":17016,"library":"hookified","title":"Hookified: Event Emitting and Middleware Hooks","description":"Hookified provides a robust solution for integrating event emitting and middleware-style hooks into JavaScript and TypeScript applications. Currently at version 2.1.1, the library maintains an active release cadence, with frequent updates addressing performance, type improvements, and feature enhancements. Key differentiators include its dual support for asynchronous and synchronous middleware hooks, serving as a simple yet powerful EventEmitter replacement. It offers comprehensive support for ESM and CJS module systems, along with TypeScript types and compatibility with Node.js 20+ environments, including browser support via CDN. The library boasts a lean footprint with no external package dependencies and a small size (around 250KB), focusing on fast and efficient execution, as evidenced by its benchmarks. Features like WaterfallHook for data transformation, robust error handling, options for enforcing hook naming conventions (enforceBeforeAfter), and built-in mechanisms for managing deprecated hooks (deprecatedHooks, allowDeprecated) further distinguish its capabilities.","status":"active","version":"2.1.1","language":"javascript","source_language":"en","source_url":"https://github.com/jaredwray/hookified","tags":["javascript","hooks","eventemitter","eventemitter3","middleware","events","event-hooks","hook-system","typescript"],"install":[{"cmd":"npm install hookified","lang":"bash","label":"npm"},{"cmd":"yarn add hookified","lang":"bash","label":"yarn"},{"cmd":"pnpm add hookified","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Hookified is an ESM-first package. While it supports CJS, direct `require` might lead to issues in some setups. Use `import` for best compatibility, especially in modern Node.js environments and TypeScript projects.","wrong":"const Hookified = require('hookified');","symbol":"Hookified","correct":"import { Hookified } from 'hookified';"},{"note":"The `Hook` class is used to create structured hook entries. `IHook` is an interface describing the hook structure.","wrong":"import { IHook } from 'hookified'; // IHook is an interface, not a class for direct instantiation","symbol":"Hook","correct":"import { Hook } from 'hookified';"},{"note":"Used for creating sequential data transformation pipelines, it implements the `IHook` interface.","symbol":"WaterfallHook","correct":"import { WaterfallHook } from 'hookified';"}],"quickstart":{"code":"import { Hookified, Hook, WaterfallHook } from 'hookified';\n\nclass MyService extends Hookified {\n  constructor() {\n    super();\n    this.data = { count: 0, items: [] };\n  }\n\n  async processData(input: string) {\n    await this.hook('beforeProcess', input);\n    this.data.count++;\n    this.data.items.push(input.toUpperCase());\n    await this.hook('afterProcess', this.data);\n    return this.data;\n  }\n\n  async saveData(initialData: any) {\n    const processedData = await this.hook('saveData', initialData);\n    console.log('Final data saved:', processedData); \n    this.emit('dataSaved', processedData); \n  }\n}\n\nconst service = new MyService();\n\n// Register a standard hook handler for 'beforeProcess'\nservice.onHook({ \n  event: 'beforeProcess',\n  handler: async (input: string) => {\n    console.log(`[Hook: beforeProcess] Input received: ${input}`);\n  }\n});\n\n// Register another hook for 'afterProcess'\nservice.onHook('afterProcess', (data: { count: number }) => {\n  console.log(`[Hook: afterProcess] Current count: ${data.count}`);\n});\n\n// Register a WaterfallHook for 'saveData' to transform data sequentially\nconst transformHook = new WaterfallHook('saveData', ({ args, results }) => {\n  const currentResult = results.length > 0 ? results[results.length - 1].result : args[0];\n  const transformed = { ...currentResult, timestamp: Date.now(), processedBy: 'transformHook' };\n  console.log('[WaterfallHook: saveData] Data transformed.');\n  return transformed;\n});\nservice.onHook(transformHook);\n\n// Register an event listener\nservice.on('dataSaved', (data) => {\n  console.log(`[Event: dataSaved] Event emitted with data: ${JSON.stringify(data)}`);\n});\n\n// Run the service\n(async () => {\n  console.log('--- Starting processData ---');\n  await service.processData('hello world');\n  await service.processData('hookified');\n  console.log('--- Finished processData ---\\n');\n\n  console.log('--- Starting saveData ---');\n  await service.saveData({ id: 1, value: 'initial' });\n  console.log('--- Finished saveData ---');\n\n  // Example of using a hook programmatically (equivalent to onHook but for single use)\n  await service.onceHook(new Hook('afterProcess', (data) => {\n    console.log(`[Hook: onceHook] Special one-time afterProcess hook triggered with count: ${data.count}`);\n  }));\n  await service.processData('one-time-hook-test');\n})();","lang":"typescript","description":"This quickstart demonstrates instantiating Hookified, registering standard and WaterfallHooks, emitting and listening to events, and processing data through the hook lifecycle."},"warnings":[{"fix":"Update all instances of `this.logger` to `this.eventLogger`.","message":"The `logger` property was renamed to `eventLogger` in v2.0.0 to prevent naming conflicts. Code accessing `this.logger` directly will fail.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Migrate usage to `throwOnHookError` in the constructor options or instance property.","message":"The `throwHookErrors` option was removed in v2.0.0. Its functionality is now integrated into `throwOnHookError`.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Ensure an 'error' event listener is always registered, or explicitly set `throwOnEmptyListeners: false` in the `Hookified` constructor options if you want to suppress this behavior.","message":"The default value for `throwOnEmptyListeners` changed to `true` in v2.0.0. This means emitting an 'error' event without any registered listeners will now throw an exception, aligning with Node.js EventEmitter behavior.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Review the migration guide from v1 to v2 on the project's GitHub page for details on adapting custom hook implementations or extending `Hookified`.","message":"Version 2.0.0 introduced significant changes to internal types and interfaces, including `Hook` type becoming `HookFn`, `onHook` accepting `IHook` objects, and `hooks` being stored as an array of `IHook`. While direct usage of `Hookified` should mostly be compatible, custom hook implementations or deep integrations might require adjustments.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Prefix all custom hook names (e.g., `beforeSave`, `afterValidation`) or set `enforceBeforeAfter: false` if this naming convention is not desired.","message":"Enabling `enforceBeforeAfter` in constructor options (e.g., `new Hookified({ enforceBeforeAfter: true })`) requires all hook names to explicitly start with 'before' or 'after'. Not adhering to this convention will result in runtime errors.","severity":"gotcha","affected_versions":">=1.12.1"},{"fix":"Use `.hook()` for general hook execution. Only use `.hookSync()` when strictly needing to process only synchronous handlers.","message":"The `.hookSync()` method explicitly skips asynchronous (async/await) handler functions. If you need to execute both synchronous and asynchronous handlers, use the `.hook()` method, which is the preferred and more comprehensive approach.","severity":"gotcha","affected_versions":">=1.15.0"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Change references from `myInstance.logger` to `myInstance.eventLogger` or `this.eventLogger`.","cause":"Attempting to call or access the `logger` property which was renamed to `eventLogger` in Hookified v2.0.0.","error":"TypeError: 'logger' is not a function"},{"fix":"Rename your hook to `beforeMyCustomHook` or `afterMyCustomHook`, or disable the enforcement by passing `{ enforceBeforeAfter: false }` to the `Hookified` constructor.","cause":"The `enforceBeforeAfter` option is set to `true`, requiring all hook names to follow a 'before:' or 'after:' prefix convention, and the provided hook name does not.","error":"Error: Hook event \"myCustomHook\" must start with \"before\" or \"after\" when enforceBeforeAfter is enabled"},{"fix":"Refactor your module to use ESM `import { Hookified } from 'hookified';` or use dynamic imports `import('hookified').then(module => new module.Hookified());` if you must remain in CJS.","cause":"Using `const Hookified = require('hookified');` in a CommonJS module when the library is primarily designed for ESM, leading to module interoperability issues.","error":"TypeError: Hookified is not a constructor (in CommonJS context)"},{"fix":"Add an 'error' event listener: `myInstance.on('error', (err) => { console.error('Caught error:', err); });` or set `new Hookified({ throwOnEmptyListeners: false })` to suppress this behavior.","cause":"An 'error' event was `emit`ted, and the `throwOnEmptyListeners` option (which defaults to `true` in v2+) is enabled, but no listener was registered for the 'error' event.","error":"UnhandledPromiseRejectionWarning: Error: No listeners for event: error"}],"ecosystem":"npm","meta_description":null}