mage-obsidian
Version:
Mage Obsidian Ultis
138 lines (120 loc) • 5.26 kB
JavaScript
class InterceptorManager {
constructor() {
this.interceptors = {};
}
/**
* Register an interceptor
* @param {string} target - The name of the target function/method to intercept
* @param {string} name - Unique name for the interceptor
* @param {string} type - 'before', 'around', 'after'
* @param {Function} handler - The function to execute
* @param {number} sortOrder - Order of execution
*/
addInterceptor(target, name, type, handler, sortOrder = 10) {
if (!this.interceptors[target]) {
this.interceptors[target] = { before: [], around: [], after: [] };
}
if (!['before', 'around', 'after'].includes(type)) {
throw new Error(`Invalid interceptor type: ${type}`);
}
this.interceptors[target][type].push({ name, handler, sortOrder });
this.interceptors[target][type].sort((a, b) => a.sortOrder - b.sortOrder);
}
/**
* Execute the intercepted method chain synchronously
* @param {string} target - The target method name
* @param {Function} originalMethod - The original function
* @param {Object} context - The 'this' context
* @param {Array} args - Arguments passed to the function
*/
executeSync(target, originalMethod, context, ...args) {
const interceptors = this.interceptors[target] || { before: [], around: [], after: [] };
// Execute 'before' interceptors
for (const interceptor of interceptors.before) {
const result = interceptor.handler.apply(context, args);
if (Array.isArray(result)) {
args = result;
}
}
// Execute 'around' interceptors
let methodToExecute = (...currentArgs) => {
return originalMethod.apply(context, currentArgs);
};
if (interceptors.around.length > 0) {
const aroundInterceptors = [...interceptors.around].reverse();
for (const interceptor of aroundInterceptors) {
const next = methodToExecute;
methodToExecute = (...currentArgs) => {
return interceptor.handler.apply(context, [next, ...currentArgs]);
};
}
}
let result = methodToExecute(...args);
// Execute 'after' interceptors
for (const interceptor of interceptors.after) {
result = interceptor.handler.apply(context, [result, ...args]);
}
return result;
}
/**
* Execute the intercepted method chain
* @param {string} target - The target method name
* @param {Function} originalMethod - The original function
* @param {Object} context - The 'this' context
* @param {Array} args - Arguments passed to the function
*/
async execute(target, originalMethod, context, ...args) {
const interceptors = this.interceptors[target] || { before: [], around: [], after: [] };
// Execute 'before' interceptors
// Before interceptors can modify args by returning an array
for (const interceptor of interceptors.before) {
const result = await interceptor.handler.apply(context, args);
if (Array.isArray(result)) {
args = result;
}
}
// Execute 'around' interceptors
// Around interceptors receive (proceed, ...args)
let methodToExecute = async (...currentArgs) => {
return await originalMethod.apply(context, currentArgs);
};
// Wrap around interceptors: first registered is outer-most
if (interceptors.around.length > 0) {
const aroundInterceptors = [...interceptors.around].reverse();
for (const interceptor of aroundInterceptors) {
const next = methodToExecute;
methodToExecute = async (...currentArgs) => {
return await interceptor.handler.apply(context, [next, ...currentArgs]);
};
}
}
let result = await methodToExecute(...args);
// Execute 'after' interceptors
// After interceptors receive (result, ...args) and must return result
for (const interceptor of interceptors.after) {
result = await interceptor.handler.apply(context, [result, ...args]);
}
return result;
}
/**
* Create a proxy to intercept method calls on an object
* @param {Object} target - The target object (e.g. module exports)
* @param {string} namespace - Namespace for interceptors
* @param {boolean} useAsync - Whether to use async execution
*/
intercept(target, namespace, useAsync = true) {
return new Proxy(target, {
get: (obj, prop) => {
const value = obj[prop];
if (typeof value === 'function') {
return (...args) => {
const method = useAsync ? this.execute : this.executeSync;
return method.call(this, `${namespace}::${String(prop)}`, value, obj, ...args);
};
}
return value;
}
});
}
}
export default new InterceptorManager();