UNPKG

ts-observe

Version:

A simple library to create wrapping middlewares for all sort of functions, classes, methods and accessors. This library enables you to decorate (wrap for standalone functions) with before and after middlewares. This approach let's you abstract from the co

196 lines 9.44 kB
"use strict"; // ======================================================================== // 1. Core Wrapping Logic // ======================================================================== Object.defineProperty(exports, "__esModule", { value: true }); exports.observe = observe; exports.ObserveMethod = ObserveMethod; exports.ObserveAccessor = ObserveAccessor; exports.ObserveClass = ObserveClass; function observe(fn, executeBefore = [], executeAfter = [], context) { if (!context) { // Provide a default context if used standalone context = { kind: 'function', name: fn.name || 'anonymous', target: {} }; } const ctxInfo = `[${context.kind}:${String(context.name)}]`; return function (...args) { let processedArgs = args; try { executeBefore.forEach((beforeFn) => { // Use .call() to pass 'this', 'context', and spread arguments correctly processedArgs = beforeFn.call(this, context, ...processedArgs); if (!Array.isArray(processedArgs)) { console.warn(`Before middleware for '${ctxInfo}' did not return array. Reverting.`, { middleware: beforeFn.name }); processedArgs = args; // Keep original args if middleware failed } }); } catch (e) { console.error(`Error during 'before' middleware for '${ctxInfo}':`, e); throw new Error(`Failed during 'before' middleware for '${ctxInfo}'`); } let result; try { // Call the original function using apply to preserve 'this' and pass processed args result = fn.apply(this, processedArgs); } catch (e) { console.error(`Error during original execution for '${ctxInfo}':`, e); throw e; // Re-throw original error for better stack traces } try { executeAfter.forEach((afterFn) => { // .call() is correct here as MethodResultMiddleware expects (this, context, result) result = afterFn.call(this, context, result); }); } catch (e) { console.error(`Error during 'after' middleware for '${ctxInfo}':`, e); throw new Error(`Failed during 'after' middleware for '${ctxInfo}'`); } return result; }; } // ======================================================================== // 3. Specific Decorator Factories // ======================================================================== // 3.1. For Methods function ObserveMethod(config = {}) { const { beforeArgs = [], afterResult = [] } = config; return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; if (typeof originalMethod === 'function') { const context = { kind: 'method', name: propertyKey, target: target }; descriptor.value = observe(originalMethod, beforeArgs, afterResult, context); return descriptor; } else { console.warn(`ObserveMethod applied to non-method property: ${String(propertyKey)}`); } }; } // 3.2 For Accessors function ObserveAccessor(config = {}) { const { beforeGet = [], afterGet = [], beforeSet = [], afterSet = [] } = config; return function (target, propertyKey, descriptor) { const originalGetter = descriptor.get; const originalSetter = descriptor.set; let newGetter = originalGetter; let newSetter = originalSetter; let changed = false; // Wrap Getter if (typeof originalGetter === 'function') { const getterContext = { kind: 'getter', name: propertyKey, target: target }; // Getters have no 'beforeArgs' middleware, pass empty array newGetter = observe(originalGetter, beforeGet, afterGet, getterContext); changed = true; } // Wrap Setter if (typeof originalSetter === 'function') { const setterContext = { kind: 'setter', name: propertyKey, target: target }; // Adapt beforeSet middleware (which expect a single value) // to the MethodArgsMiddleware signature (which expect ...args) const adaptedBeforeSet = beforeSet.map(middleware => { const adapter = function (ctx, ...args) { const originalValue = args[0]; const processedValue = middleware.call(this, ctx, originalValue); return [processedValue]; }; return adapter; }); // Pass the adapted middleware to observe newSetter = observe(originalSetter, adaptedBeforeSet, afterSet, setterContext); changed = true; } // Assign back to descriptor if changed if (changed) { descriptor.get = newGetter; descriptor.set = newSetter; return descriptor; } else { console.warn(`ObserveAccessor applied to non-accessor property: ${String(propertyKey)}`); } }; } // 3.3. For Classes (Constructors) function ObserveClass(config = {}) { const { beforeArgs = [], afterInstance = [] } = config; return function (OriginalConstructor) { if (typeof OriginalConstructor !== 'function' || !OriginalConstructor.prototype) { console.warn(`ObserveClass applied to non-class target.`); return OriginalConstructor; } const classContext = { kind: 'class', name: OriginalConstructor.name, target: OriginalConstructor }; // Define the new class extending the original const NewConstructor = class extends OriginalConstructor { constructor(...args) { let processedArgs = args; // Process beforeArgs (BEFORE super call) try { beforeArgs.forEach(middleware => { // Use apply for ConstructorArgsMiddleware (this is null) processedArgs = middleware.apply(null, [classContext, ...processedArgs]); if (!Array.isArray(processedArgs)) { console.warn(`Constructor '${classContext.name.toString()}' beforeArgs middleware did not return array. Reverting.`); processedArgs = args; } }); } catch (e) { console.error(`Error during constructor '${classContext.name.toString()}' 'beforeArgs' middleware:`, e); throw new Error(`Failed during constructor '${classContext.name.toString()}' 'beforeArgs' middleware`); } // Call super() - invokes the OriginalConstructor logic super(...processedArgs); // Process afterInstance (AFTER super call) - 'this' is now available try { let instance = this; afterInstance.forEach(middleware => { // Use call for ConstructorInstanceMiddleware (this is the instance) const potentiallyModifiedInstance = middleware.call(this, classContext, instance); // Allow middleware to replace the instance if it returns a value instance = potentiallyModifiedInstance !== undefined ? potentiallyModifiedInstance : instance; }); // Note: If middleware replaced the instance, the original 'this' is discarded. // This is advanced usage and generally not recommended unless necessary. } catch (e) { console.error(`Error during constructor '${classContext.name.toString()}' 'afterInstance' middleware:`, e); throw new Error(`Failed during constructor '${classContext.name.toString()}' 'afterInstance' middleware`); } } }; // --- Preserve Static Members & Prototype Chain --- function copyStatics(source, target) { let currentSource = source; const copiedKeys = new Set(); while (currentSource && currentSource !== Function.prototype) { Reflect.ownKeys(currentSource).forEach(key => { if (key !== 'prototype' && key !== 'length' && key !== 'name' && key !== 'arguments' && key !== 'caller' && !copiedKeys.has(key)) { const descriptor = Object.getOwnPropertyDescriptor(currentSource, key); if (descriptor && (descriptor.configurable || descriptor.writable)) { Object.defineProperty(target, key, descriptor); copiedKeys.add(key); } } }); currentSource = Object.getPrototypeOf(currentSource); } } copyStatics(OriginalConstructor, NewConstructor); Object.setPrototypeOf(NewConstructor.prototype, OriginalConstructor.prototype); Object.setPrototypeOf(NewConstructor, OriginalConstructor); return NewConstructor; }; } //# sourceMappingURL=index.js.map