Event System #
Most of the internal tasks performed by Kuzzle trigger events.
Kuzzle enables to attach business-logic to these events by defining hooks (which allow to perform additional actions when the event triggers) and pipes (which change the behavior of the standard logic when the event triggers).
The complete list of events is available here: Internal Events List
You can display events triggered by Kuzzle by setting the DEBUG
environment variable to kuzzle:events:*
.
Pipe #
Kuzzle allows to modify API actions behavior with a very precise middleware-like system.
This system allows to modify the execution flow of requests processed by Kuzzle.
Pipes are functions plugged to events, called synchronously by Kuzzle, and receiving information regarding that event.
Pipes can:
- Change the received information. Kuzzle will use the updated information upon resuming the task
- Abort a task. If a pipe throws an error, Kuzzle interrupts the task, and forwards a standardized version of the thrown error to the originating client
Each event carries a different payload. This payload must be returned by the pipe function so Kuzzle can continue its execution process.
The execution order of the pipes is decided by Kuzzle at runtime. Your pipes should be independent from one another.
Examples of pipes usage:
- dynamic right restrictions
- synchronize with another database
- hidding sensitive information from the response
Registering a pipe #
We need to use the Backend.pipe.register method to register new pipes. This method takes an event name as its first parameter, followed by the pipe handler function.
Each event has a different payload.
The pipe handler function must return a promise resolving to the received payload.
It is possible to register several pipes on the same event by calling several times the Backend.pipe.register method.
When an event has more than one payload then only the first argument of the handler function must be returned. (e.g. Generic Document Events)
Example: Changing the result of the server:now API action
import { KuzzleRequest } from 'kuzzle';
app.pipe.register('server:afterNow', async (request: KuzzleRequest) => {
request.result.now = (new Date()).toUTCString();
return request;
});
As pipes are executed synchronously by Kuzzle, they can increase the execution time of a request.
A pipe that takes a long time to execute will generate an alert message in the logs.
This warning can be configured under the plugins.pipeWarnTime configuration key.
Aborting a task #
When the pipe handler function returns a rejected promise or throws an error, Kuzzle aborts the current task.
If the error is one of the available default errors then the response returned to the client will contain the error as is, otherwise the error will be wrapped in a PluginImplementationError error.
Example: Limit reading access to documents to their creator
import { Document, KuzzleRequest, Backend, ForbiddenError } from 'kuzzle';
app.pipe.register(
'generic:document:afterGet',
async (documents: Document[], request: KuzzleRequest) => {
for (const document of documents) {
if (request.getKuid() !== document._source._kuzzle_info.creator) {
throw new ForbiddenError('Unauthorized access');
}
}
return documents;
});
Generic Document Events have a payload consisting of two arguments: an array of documents and the original KuzzleRequest object
Hooks #
Kuzzle allows to execute additional logic upon events.
Hooks are functions plugged to events, called asynchronously by Kuzzle, and receiving information regarding that event.
In general, hooks are used to perform background tasks which may otherwise slow down the request execution process.
Examples of hooks usage:
- enrich the request with external information
- notify user registration
Registering a hook #
We need to use the Backend.hook.register method to register new hooks. This method takes an event name as its first parameter, followed by the hook handler function.
It is possible to register several hooks on the same event by calling several times the Backend.hook.register method.
Example: Use the pub/sub engine to log user registration
app.hook.register('security:afterCreateRestrictedUser', async (request: KuzzleRequest) => {
app.log.info(`New user registered: ${JSON.stringify(request.getUser())}`);
});
Handling errors #
When a hook handler function returns a rejected promise or throw an error then the hook:onError is triggered.
Handler function attached to this event will receive the following arguments:
Arguments | Type | Description |
---|---|---|
pluginName | String | Application or plugin name |
event | String | Original event to which the hook was attached |
error | Error | Error object |
app.hook.register(
'hook:onError',
async (pluginName: string, event: string, error: Error) => {
app.log.error(`Error occured on event "${event}": ${error}`);
});
To prevent infinite loops, if a hook attached to the hook:onError
event fails, it won't trigger any other events.
Trigger Events #
You can only trigger custom events during the runtime
phase, after the application has started.
Internal or custom events can be triggered with the Backend.trigger method.
Pipes and hooks can be plugged on custom events as well as on internal events.
It's considered a good practice to prefix your event name with your application name.
Example: Trigger a custom event
await app.trigger('app-name/file-available', fileUrl);
If an internal event is triggered, the payload must be the same as the original event.
Events are not triggered with the embedded SDK and controllers actions.
By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. On the contrary, controllers accessed by the external SDK through HTTP or Websocket requests will always fire events.
This behaviour is set in order to prevent an infinite loop in which a pipe calls a controller generating an event calling this same pipe again and again.
It is nonetheless possible to pass the flag triggerEvents
to the options parameters of the controller to fire events :
await this.sdk.document.create(
"index",
'collection',
{
contentOfDocument: 'CREATED VIA CONTROLLER',
},
_idOfDocument,
{ triggerEvents: true },
);