'use strict'; /** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */ export function compose(...funcs) { if (funcs.length === 0) { return arg => arg; } funcs = funcs.filter(func => typeof func === 'function'); if (funcs.length === 1) { return funcs[0]; } const last = funcs[funcs.length - 1]; const rest = funcs.slice(0, -1); return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)); } let targetsCache = []; /** * Apply middleweare to an object. * Middleware functions are functions that have access to the target function and it's arguments, * and the target object and the next middleware function in the target function cycle. * The next middleware function is commonly denoted by a variable named next. * * Middleware functions can perform the following tasks: * - Execute any code. * - Make changes to the function's arguments. * - End the target function. * - Call the next middleware in the stack. * * If the current middleware function does not end the target function cycle, * it must call next() to pass control to the next middleware function. Otherwise, * the target function will be left hanging. * * e.g. * ``` * const walk = target => next => (...args) => { * this.log(`walk function start.`); * const result = next(...args); * this.log(`walk function end.`); * return result; * } * ``` * * Middleware object is an object that contains function's name as same as the target object's function name. * * e.g. * ``` * const Logger = { * walk: target => next => (...args) => { * console.log(`walk function start.`); * const result = next(...args); * console.log(`walk function end.`); * return result; * } * } * ``` * * Function's name start or end with "_" will not be able to apply middleware. * * @example * * // the target object * class Person { * // the target function * walk(step) { * this.step = step; * } * } * * // middleware for walk function * const logger = target => next => (...args) => { * this.log(`walk start, steps: ${args[0]}.`); * const result = next(...args); * this.log(`walk end.`); * return result; * } * * // apply middleware to target object * const p = new Person(); * const applyMiddleware = new ApplyMiddleware(p); * applyMiddleware.use('walk', walk); * p.walk(); * */ export class ApplyMiddleware { /** * @param {object} target The target object. * @param {...object} middlewareObjects Middleware objects. * @return {object} this */ constructor(target, ...middlewareObjects) { let instance = targetsCache.find(function (key) { return key === target; }) || this; instance._target = instance._target || target; instance._methods = instance._methods || {}; instance._methodMiddlewares = instance._methodMiddlewares || {}; instance.use(...middlewareObjects); return instance; } _applyToMethod(methodName, ...middlewares) { if (typeof methodName === 'string' && !/^_+|_+$/g.test(methodName)) { let method = this._methods[methodName] || this._target[methodName]; if (typeof method === 'function') { this._methods[methodName] = method; if (this._methodMiddlewares[methodName] === undefined) { this._methodMiddlewares[methodName] = []; } middlewares.forEach(middleware => typeof middleware === 'function' && this._methodMiddlewares[methodName].push(middleware(this._target)) ); this._target[methodName] = compose(...this._methodMiddlewares[methodName])(method.bind(this._target)); } } } /** * Apply middleware to the target object. * @param {string|object} methodName String for target function name, object * @param {...function} middlewares The middleware chain to be applied. * @return {object} this */ use(methodName, ...middlewares) { if (typeof methodName === 'object') { Array.prototype.slice.call(arguments).forEach(arg => { // A middleweare object can specify targer functions within middlewareMethods (Array). // e.g. obj.middlewareMethods = ['method1', 'method2']; // only method1 and method2 will be the target function. typeof arg === 'object' && (arg.middlewareMethods || Object.keys(arg)).forEach(key => { this._applyToMethod(key, arg[key].bind(arg)); }); }); } else { this._applyToMethod(methodName, ...middlewares); } return this; } }