The hookable and getValue functions
Overview
hookable
is a function. It is a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. They helps extensions interact with EverShop Core without modifying the core code.
hookable
allows you to define your function and then hook it into the core code. When the core code runs, it will call your function at the appropriate time. It allows you to run your code before or after the core code runs.
Example of hookable
const {
insert
} = require('@evershop/postgres-query-builder');
const { hookable } = require('@evershop/evershop/src/lib/util/hookable');
async function insertCustomerData(data, connection) {
const customer = await insert('customer').given(data).execute(connection);
// Delete password from customer object
delete customer.password;
return customer;
}
// Insert customer data
const customer = await hookable(insertCustomerData, {
...context,
connection
})(customerData, connection);
The hookable
function accepts two parameters: the original function and the context object. The context object contains the data that will be passed to the callback function.
Using hookBefore
Now let's say you are developing an extension and you want to run some code before or after the insertCustomerData
function runs. You can use hookBefore
or hookAfter
to do that.
const { hookBefore } = require('@evershop/evershop/src/lib/util/hookable');
function hookBefore('insertCustomerData', (context) => {
// Your code here. It will run before insertCustomerData function runs
}, 10);
The hookBefore
function accepts three parameters: the original function name that you want to hook into, the callback function and the priority. The priority is a number that determines the order in which the callback functions will be executed. The lower the number, the earlier the callback function will be executed. The callback function will be executed in ascending order of priority and have the access to the context object.
Using hookAfter
The hookAfter
function works the same way as hookBefore
but it will run after the original function runs.
const { hookAfter } = require('@evershop/evershop/src/lib/util/hookable');
function hookAfter('insertCustomerData', (context) => {
// Your code here. It will run after insertCustomerData function runs
}, 10);
getValue
function
The getValue
is a function that allows you to create a value and let other extensions modify it. It is a way for one piece of code to change/modify a created value without modifying the original code.
Let's use the create customer scenario as an example. You want to create a customer and then let other extensions modify the customer object before it is inserted into the database. You can use getValue
to do that.
const {
getValueSync,
getValue
} = require('@evershop/evershop/src/lib/util/registry');
const data = {
full_name: 'John Doe',
email: 'john@evershop.io'
};
const context = {
connection
};
const customerData = await getValue(
'customerDataBeforeCreate',
data,
context
);
// Insert the customer data to the database
const customer = await insertCustomerData(customerData, connection);
So, let's say your extension wants to modify the customer object before it is inserted into the database. You can use addProcessor
to do that.
const {
addProcessor
} = require('@evershop/evershop/src/lib/util/registry');
addProcessor('customerDataBeforeCreate', (value) => {
return {
full_name: 'Jane Doe - Modified',
email: 'jane@evershop.io'
}
}, 10);
The addProcessor
function accepts four parameters: the value name, the callback function, the priority and the validator function. The callback function will be executed in ascending order of priority and have the access to the value object.
The value returned from the callback function will be passed to the next callback function. The last callback function will return the final value that will be used in the core code.
Some times you want to make sure the value must meet some conditions, you can use the validator
function. The validator
function will be executed and validate the returned value. If the validator function returns false
, an error will be thrown.
In the example above, if you want to make sure the data is an object with full_name
and email
properties, you can use the validator
function.
const {
getValueSync,
getValue
} = require('@evershop/evershop/src/lib/util/registry');
const data = {
full_name: 'John Doe',
email: 'john@evershop.io'
};
const context = {
connection
};
const customerData = await getValue(
'customerDataBeforeCreate',
data,
context,
(value) => {
if (typeof value !== 'object' || !value.full_name || !value.email) {
return false;
}
return true;
}
);
The callback function will be executed with the context as the this
object. If the getValue
function is called multiple times with the same value name and the same context, the callback functions will not be executed again. The value will be cached and returned from the cache.
If you want to have a processor
function that will be executed at very last, you can use the addFinalProcessor
function. This to make sure that no other processor will modify the value after your processor runs.
const {
addFinalProcessor
} = require('@evershop/evershop/src/lib/util/registry');
addFinalProcessor('customerDataBeforeCreate', (value) => {
return {
full_name: 'My final name',
email: 'jane@evershop.io'
}
}, 10);
getValueSync
function
The getValueSync
function works the same way as getValue
. The only difference is that getValueSync
will execute the callback functions synchronously. It only accepts the synchronous callback functions.
const {
getValueSync
} = require('@evershop/evershop/src/lib/util/registry');
const data = {
full_name: 'John Doe',
email: 'john@evershop.io'
};
const context = {
connection
};
const customerData = getValueSync(
'customerDataBeforeCreate',
data,
context
);
// Insert the customer data to the database
const customer = await insertCustomerData(customerData, connection);