@overextended/ox_lib
Version:
JS/TS wrapper for ox_lib exports
151 lines (150 loc) • 5.32 kB
JavaScript
import { cache } from '../cache';
const hooks = new Set();
on('onResourceStop', (resource) => {
for (let hook of hooks)
hook.remove(resource);
});
export class HookPipeline {
hooks;
event;
filter;
/**
* Creates a hook pipeline for a specific event.
* The pipeline manages a collection of registered hooks and controls execution
* flow through filtering, rejection, and dispatching.
*
* It also exposes external resource hooks:
* - `registerHook:<event>` adds a hook to the pipeline
* - `removeHook:<event>` removes a hook from the pipeline
*/
constructor(event, filter) {
this.hooks = [];
this.event = event;
this.filter = filter;
hooks.add(this);
exports(`registerHook:${event}`, (ref, options) => this.registerHook(ref, options));
exports(`removeHook:${event}`, (hookId) => {
const resource = GetInvokingResource() || cache.resource;
this.remove(resource, hookId);
});
}
/**
* Registers a hook into the pipeline for the current event.
* @param options Optional metadata attached to the hook.
*/
registerHook(cb, options) {
const idx = this.hooks.length;
const resource = GetInvokingResource() || cache.resource;
const hook = {};
if (options)
Object.assign(hook, options);
if (cb)
hook.cb = cb;
hook.resource = resource || cache.resource;
hook.hookId = `${resource}:${this.event}:${idx}`;
this.hooks.push(hook);
return hook.hookId;
}
/**
* Removes hooks from the pipeline.
* - If `hookId` is provided, only the matching hook is removed.
* - If omitted, all hooks belonging to the invoking resource are removed.
*/
remove(resource, hookId) {
for (let i = this.hooks.length - 1; i >= 0; i--) {
const hook = this.hooks[i];
if (hook.resource === resource && (!hookId || hook.hookId === hookId)) {
this.hooks.splice(i, 1);
}
}
}
/**
* Executes the hook pipeline for the payload.
*
* Each registered hook is evaluated in order of registration, checking the payload against a provided filter\
* using the hook options and executing the hook callback.
*
* A hook may block execution by returning `false` from the pipeline filter or its own callback.
*
* If any hook rejects the execution, dispatch is cancelled and `result.ok` is set to `false`.
*
* The returned object acts as a finalisation handle and emits results to registered handlers once closed.
*/
dispatch(payload) {
const events = [];
const result = {
ok: true,
size: 0,
[Symbol.dispose]: () => {
// @ts-expect-error
const packed = msgpack_pack([result.ok, payload]);
for (let event of events)
TriggerEventInternal(event, packed, packed.length);
},
};
for (let hook of this.hooks) {
const runHook = this.filter?.(hook, payload) !== false;
const rejected = runHook && hook.cb?.(payload) === false;
if (rejected) {
result.ok = false;
break;
}
if (runHook)
events.push(hook.hookId);
}
result.size = events.length;
return result;
}
}
class EventHook {
hookId;
resource;
event;
handler;
/** Creates a new EventHook instance bound to a specific exported hook. */
constructor(hookId, resource, event) {
this.hookId = hookId;
this.resource = resource;
this.event = event;
}
/**
* ---Attaches a post-execution event handler for this hook.
* The handler is triggered after the hooked event completes and receives:
* - `ok` whether the original event execution succeeded
* - `payload` the returned or processed event data
*
* If a handler is already registered, it will be replaced.
*/
on(handler) {
this.off();
this.handler = handler;
on(this.hookId, this.handler);
}
/** Detaches the currently registered post-hook event handler, if one exists. */
off() {
if (!this.handler)
return;
removeEventListener(this.hookId, this.handler);
}
/**
* Fully removes this hook from both the local event system and the external
* hook registry provided by the originating resource.
*
* This invalidates the hook instance; it should not be used afterward.
*/
remove() {
this.off();
exports[this.resource][`removeHook:${this.event}`](this.hookId);
}
}
export function registerHook(eventName, handler, options) {
const [resource, event] = eventName.split(':', 2);
if (!resource || !event)
throw new Error(`Invalid event format: ${eventName} (expected "resourceName:eventName")`);
if (handler && !options && typeof handler !== 'function') {
options = handler;
handler = null;
}
const hookId = exports[resource][`registerHook:${event}`](handler, options);
return new EventHook(hookId, resource, event);
}