Burdy uses hooks to provide extensibility. We have taken the heavy inspiration of WordPress during the development of the functionality of the hook.
You can think of them in terms of a subscriber/observer pattern, or simply s WordPress puts it:
Hooks are a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. They make up the foundation for how plugins and themes interact with WordPress Core, but they’re also used extensively by Core itself.
They are used on both front-end and back-end to provide extensibility. There are 3 types of hooks available in Burdy:
The main difference between an action and a filter can be summed up like this:
Said another way:
The something referred to is the parameter list sent via the hook definition.
Burdy
provides an export called Hooks, here is a short description of ways it can be used.
Action listener is an async callback that is called once a certain action is triggered. As a concrete example, during start-up api/init
is called and it provides an instance of the express. Application as the first and only argument. To add our own API we can do the following:
// index.ts
Hooks.addAction('api/init', async (app) => {
app.get('/hello-world', asyncWrapper(async (req, res) => {
res.send('Hello World');
}))
}, { id: 'my-server-init' });
To see your result view http://localhost:4000/hello-world.
Note that id and 3rd argument is optional. It's useful when you want to remove an action. (Hooks.removeAction('server/init', 'my-server-init')
).
Triggering an action allows you to create extensible functions which subscribers (async callbacks) can modify or react to. To trigger an action simply call the doAction with name and parameters.
// index.ts
const createdUser; //Created User
Hooks.doAction('my/createdUser', createdUser, argument2, argument3, ...);
Filters are very similar to actions, however, they return a value (mutate, and return the first argument). For example, you can use one of the provided filters to add new models to Burdy.
// index.ts
import MyModel from '../models/my-model'; // TypeORM Entity/Model
Hooks.addFilter('db/models', async (models) => [...models, MyModel]);
This will register your model to Burdy. This means that when migrations are run, this model/entity will be automatically included.
To trigger a custom filter, use applyFilter function.
// index.ts
Hooks.addFilter('addNumbers', async (a, b) => a + b);
const result = await Hooks.applyFilters('addNumbers', 5, 4); // Will have value 9 because of added filter above.
Note that this mutates only the first argument, therefore in the next hook, values would be: a = 9 and b = 4 respectively.
This is very similar to regular filters, except they are only used on the frontend and are synchronous. As an example, to add a new link to the dashboard we can use:
// admin/index.tsx
Hooks.addSyncFilter('dashboard/sections', () => ({
key: 'my-section',
component: // <MyComponent /> - // JSX Element
permissions: ['admin'] // Only admins see this section in dashboard
}))
Similarly to back-end filters, you can apply other filters in the following manner:
// admin/index.tsx
Hooks.addSyncFilter('my/filter',
(components, someArg) => [...components, someArg.map(
i => // <div>{i}</div>
)]
);
const myComponents = Hooks.applySyncFilter('my/filter', [], [1, 2, 3, 4]);
If you render myComponents you will get 4 divs, with children 1, 2, 3, and 4.
To extend the types you can refer to create-burdy-app. Essentially you need to extend a global namespace in the manner as follows:
// global.d.ts
import '@burdyjs/core/src/types/burdy'
export {};
declare global {
namespace Burdy {
interface IActions {
'someNumberHook': [number, string]; // callback type = (a: number, b: string) => string;
'myModelHook': [MyModel, string, string]; // callback type = (a: MyModel, b: string, c: string) => MyModel
}
interface IFilters {
'addNumbers': [number, number]; // callback type = (a: number, b: number) => void;
}
interface ISyncFilters {}
}
interface String {
test: () => void;
}
}