hookable
Wrap a function to make it hookable, allowing other code to run before or after it executes.
Import
import { hookable } from '@evershop/evershop/lib/util/hookable';
Syntax
hookable<T extends Function>(originalFunction: T, context?: any): T
Parameters
originalFunction
Type: T extends Function
The function to make hookable. Must be a named function.
context
Type: any (optional)
Context object passed to hook callbacks.
Return Value
Returns a proxied version of the original function that executes registered hooks.
Examples
Basic Usage
import { hookable } from '@evershop/evershop/lib/util/hookable';
import { insert } from '@evershop/postgres-query-builder';
async function insertCustomerData(data, connection) {
const customer = await insert('customer')
.given(data)
.execute(connection);
delete customer.password;
return customer;
}
// Make the function hookable
const customer = await hookable(insertCustomerData, { connection })(
customerData,
connection
);
With Context
import { hookable } from '@evershop/evershop/lib/util/hookable';
async function createProduct(data, connection) {
const product = await insert('product')
.given(data)
.execute(connection);
return product;
}
// Pass context to hooks
const product = await hookable(createProduct, {
connection,
userId: 123,
source: 'admin'
})(productData, connection);
In Service Function
import { hookable } from '@evershop/evershop/lib/util/hookable';
import { getConnection } from '@evershop/evershop/lib/postgres';
async function insertOrderData(data, connection) {
return await insert('order')
.given(data)
.execute(connection);
}
export async function createOrder(orderData) {
const connection = await getConnection();
try {
const order = await hookable(insertOrderData, {
connection
})(orderData, connection);
await commit(connection);
return order;
} catch (error) {
await rollback(connection);
throw error;
}
}
Multiple Hookable Functions
import { hookable } from '@evershop/evershop/lib/util/hookable';
async function validateData(data) {
// Validation logic
return data;
}
async function saveData(data, connection) {
// Save logic
return await insert('table').given(data).execute(connection);
}
async function processData(data) {
const connection = await getConnection();
// Both functions are hookable
const validated = await hookable(validateData)(data);
const saved = await hookable(saveData, { connection })(
validated,
connection
);
return saved;
}
Complete Example
import { hookable } from '@evershop/evershop/lib/util/hookable';
import { hookBefore, hookAfter } from '@evershop/evershop/lib/util/hookable';
import { insert } from '@evershop/postgres-query-builder';
// Core function
async function createProduct(data, connection) {
const product = await insert('product')
.given(data)
.execute(connection);
return product;
}
// Make it hookable
export async function productService(data, connection) {
return await hookable(createProduct, { connection })(
data,
connection
);
}
// Extensions can register hooks
hookBefore('createProduct', async (data) => {
if (!data.sku) {
throw new Error('SKU is required');
}
}, 5);
hookAfter('createProduct', async (product) => {
console.log('Product created:', product.product_id);
}, 10);
Named Functions Required
The function must be named for hooks to work:
import { hookable } from '@evershop/evershop/lib/util/hookable';
// ✅ Correct - named function
async function saveOrder(data, connection) {
return await insert('order').given(data).execute(connection);
}
const order = await hookable(saveOrder, { connection })(data, connection);
// ❌ Wrong - anonymous function
const order = await hookable(async (data, connection) => {
return await insert('order').given(data).execute(connection);
})(data, connection);
Hook Registration
Hooks are registered separately using hookBefore and hookAfter:
import { hookBefore, hookAfter } from '@evershop/evershop/lib/util/hookable';
// Register before hook
hookBefore('createProduct', async (data) => {
data.slug = generateSlug(data.name);
}, 5);
// Register after hook
hookAfter('createProduct', async (product) => {
await syncToSearchEngine(product);
}, 10);
Context Object
Pass context to provide additional data to hooks. The context is bound to this inside hook callbacks, so you must use regular functions (not arrow functions) to access it:
import { hookable } from '@evershop/evershop/lib/util/hookable';
const result = await hookable(myFunction, {
connection,
userId: request.session.userId,
permissions: request.session.permissions
})(data, connection);
// Hooks access context via 'this' — use a regular function, NOT an arrow function
hookBefore('myFunction', async function(data, connection) {
if (!this.permissions.includes('create')) {
throw new Error('Unauthorized');
}
});
Arrow functions (=>) do not have their own this binding. If you use an arrow function as a hook callback, this will be undefined and you will not be able to access the context. Always use the function keyword when your hook needs context access.
Notes
- Original function must be a named function
- Returns proxied version that executes hooks
- Context is optional but useful for passing metadata
- Hooks are registered separately with
hookBeforeandhookAfter - Both sync and async functions are supported
- Function name is used to match hooks
See Also
- hookBefore - Register before hooks
- hookAfter - Register after hooks