@arraypress/hook-registry
v1.0.0
Published
Framework-agnostic event and hook system for plugin architectures. Supports actions, filters, and exclusive handlers with priority ordering.
Maintainers
Readme
@arraypress/hook-registry
Framework-agnostic event and hook system for plugin architectures. Supports actions, filters, and exclusive handlers with priority ordering.
Installation
npm install @arraypress/hook-registryQuick Start
import { createHookRegistry } from '@arraypress/hook-registry';
const hooks = createHookRegistry();
// Register an action handler
hooks.on('order:completed', async (payload) => {
console.log('Order completed:', payload.orderId);
});
// Emit an action (fire-and-forget)
await hooks.emit('order:completed', { orderId: '123' });
// Register a filter handler (modifies payload)
hooks.on('email:beforeSend', async (email) => {
return { ...email, subject: '[Store] ' + email.subject };
});
const email = await hooks.filter('email:beforeSend', {
to: '[email protected]',
subject: 'Your order',
});
// email.subject === '[Store] Your order'Hook Types
Actions (emit)
Fire-and-forget events. All handlers run in priority order, return values are ignored. Use for side effects like sending notifications, logging, or syncing data.
hooks.on('order:completed', async (order) => {
await sendSlackNotification(order);
}, { priority: 10 });
hooks.on('order:completed', async (order) => {
await updateAnalytics(order);
}, { priority: 20 });
await hooks.emit('order:completed', order);Filters (filter)
Pipeline processing. Each handler receives the payload (or previous handler's return value), can modify it, and returns the updated value. If a handler returns undefined, the payload passes through unchanged.
hooks.on('price:calculate', async (price) => {
return { ...price, amount: Math.round(price.amount * 0.9) }; // 10% off
});
hooks.on('price:calculate', async (price) => {
return { ...price, amount: price.amount + 500 }; // add $5 shipping
});
const final = await hooks.filter('price:calculate', { amount: 10000 });
// final.amount === 9500 (10000 * 0.9 + 500)Exclusive Handlers
Only one exclusive handler can exist per hook. When a filter encounters an exclusive handler, only that handler runs. Ideal for "provider" patterns where exactly one implementation should handle the event.
// Default email delivery
hooks.exclusive('email:deliver', async (email) => {
return resendClient.send(email);
});
// Plugin overrides with its own provider
hooks.exclusive('email:deliver', async (email) => {
return postmarkClient.send(email); // replaces the previous
});Priority
Handlers run in priority order (lower numbers first). Default priority is 10. Exclusive handlers always run at priority 0.
hooks.on('init', async () => console.log('second'), { priority: 20 });
hooks.on('init', async () => console.log('first'), { priority: 5 });
hooks.on('init', async () => console.log('default')); // priority 10
// Output: first, default, secondPlugin Integration
Tag handlers with a pluginId to manage them as a group:
// Plugin registers its handlers
hooks.on('order:completed', handler1, { pluginId: 'slack-plugin' });
hooks.on('customer:created', handler2, { pluginId: 'slack-plugin' });
// Unload all handlers when the plugin is disabled
hooks.offPlugin('slack-plugin');API Reference
createHookRegistry()
Creates and returns a new HookRegistry instance.
registry.on(hook, fn, options?)
Register a handler for a hook.
hook— Hook name stringfn— Async function(payload, context?) => Promise<any>options.priority— Number (default:10, lower runs first)options.id— Unique handler ID (auto-generated if omitted)options.pluginId— Plugin ID for group management
registry.exclusive(hook, fn, options?)
Register an exclusive handler. Replaces any previous exclusive handler on the same hook.
registry.off(id)
Remove a handler by its ID.
registry.offPlugin(pluginId)
Remove all handlers registered with the given plugin ID.
registry.emit(hook, payload?, context?)
Emit an action hook. All handlers run, return values ignored. Errors are caught and logged.
registry.filter(hook, payload, context?)
Run a filter pipeline. Returns the final modified payload. If an exclusive handler exists, only it runs.
registry.has(hook)
Returns true if the hook has any registered handlers.
registry.list()
Returns an array of all hook names that have registered handlers.
Error Handling
Handler errors are caught and logged to console.error. A failing handler does not prevent subsequent handlers from running. For exclusive handlers in a filter, an error returns the unmodified payload.
License
MIT
