Skip to main content

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/evershop/lib/postgres/query';

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/evershop/lib/postgres/query';

// 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');
}
});
warning

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 hookBefore and hookAfter
  • Both sync and async functions are supported
  • Function name is used to match hooks

See Also​