- Notifications
You must be signed in to change notification settings - Fork1
Functions to wrap other functions and fields/methods and to change/enhance their behavior, functionality or usage
License
gamtiq/wrapme
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Functions to wrap other functions and fields/methods and to change/enhance their behavior, functionality or usage.
Can be used for Aspect-oriented programming.
- Wrap a single function/field/method (by
wrap
) or several fields and methods at once (byintercept
). - Wrap only field's get operation (
get
option) or set operation (set
option), or both (by default). - Provide special getter and/or setter for wrapped field if it is necessary.
- Call original function/method or field's operation before (use
before
orlisten
option),after (useafter
option) and/or insidehandler
(userun()
orrunApply()
). - Totally control calling of original function/method or field's operation inside
handler
:call depending on condition, filter/validate/convert passed arguments and/or provide another arguments. - Return result of original function/method or field's operation, or any other value from
handler
. - Save necessary data between
handler
calls. - Restore original fields/methods when it is needed.
- Does not have dependencies and can be used in ECMAScript 5+ environment.
- Small size.
import{intercept}from'wrapme';constapi={sum(...numList){letresult=0;for(letvalueofnumList){result+=value;}returnresult;},// Other methods// ...};// Loggingconstlog=[];functionlogger(callData){log.push({name:callData.field,args:callData.arg,result:callData.result,callNum:callData.number,time:newDate().getTime()});}constunwrap=intercept(api,'sum',logger,{listen:true});api.sum(1,2,3,4);// Returns 10, adds item to logapi.sum(1,-1,2,-2,3);// Returns 3, adds item to log// Restore original methodunwrap();
Installation↑
npm install wrapme
Usedist/wrapme.umd.development.js
ordist/wrapme.umd.production.min.js
(minified version).
Usage↑
import{intercept,wrap}from'wrapme';
constwrapme=require('wrapme');const{ intercept, wrap}=wrapme;
define(['path/to/dist/wrapme.umd.production.min.js'],function(wrapme){constintercept=wrapme.intercept;constwrap=wrapme.wrap;});
<scripttype="text/javascript"src="path/to/dist/wrapme.umd.production.min.js"></script><scripttype="text/javascript">// wrapme is available via wrapme field of window objectconstintercept=wrapme.intercept;constwrap=wrapme.wrap;</script>
Examples↑
import{intercept,wrap}from'wrapme';constapi={value:1,sum(...numList){letresult=0;for(letvalueofnumList){result+=value;}returnresult;},positive(...numList){letresult=[];for(letvalueofnumList){if(value>0){result.push(value);}}returnresult;},factorial(num){letresult=1;while(num>1){result*=num--;}returnresult;},binomCoeff(n,k){const{ factorial}=api;returnfactorial(n)/(factorial(k)*factorial(n-k));}};// Loggingconstlog=[];functionlogger(callData){if(!callData.byUnwrap){callData.settings.log.push({name:callData.field,args:callData.arg,result:callData.result,callNum:callData.number,time:newDate().getTime()});}}constunwrap=intercept(api,['sum','positive','value'],logger,{listen:true, log});api.sum(1,2,3,4);// Returns 10, adds item to logapi.positive(1,2,-3,0,10,-7);// Returns [1, 2, 10], adds item to logapi.value+=api.sum(1,-1,2,-2,3);// Changes value to 4, adds items to log// Restore original fieldsunwrap();api.positive(-1,5,0,api.value,-8);// Returns [5, 4], doesn't add items to logconsole.log("call log:\n",JSON.stringify(log,null,4));/* log looks like: [ { "name": "sum", "args": [ 1, 2, 3, 4 ], "result": 10, "callNum": 1, "time": 1586602348174 }, { "name": "positive", "args": [ 1, 2, -3, 0, 10, -7 ], "result": [ 1, 2, 10 ], "callNum": 1, "time": 1586602348174 }, { "name": "value", "args": [], "result": 1, "callNum": 1, "time": 1586602348174 }, { "name": "sum", "args": [ 1, -1, 2, -2, 3 ], "result": 3, "callNum": 2, "time": 1586602348174 }, { "name": "value", "args": [ 4 ], "result": 4, "callNum": 2, "time": 1586602348175 } ]*/// Simple memoizationfunctionmemoize(callData){const{ save}=callData;constkey=callData.arg.join(' ');return(keyinsave) ?save[key] :(save[key]=callData.run());}intercept(api,['factorial','binomCoeff'],memoize);api.factorial(10);api.factorial(5);api.binomCoeff(10,5);// Uses already calculated factorialsapi.binomCoeff(10,5);// Uses already calculated value// Side effectsfunctionsaveToLocalStorage(callData){if(callData.bySet){const{ save}=callData;if('id'insave){clearTimeout(save.id);}save.id=setTimeout(()=>localStorage.setItem(`wrap:${callData.field}`,typeofcallData.result==='undefined' ?callData.arg0 :callData.result),callData.settings.timeout||0);}}wrap(api,'value',saveToLocalStorage,{listen:true,timeout:50});// Validation, filtering or conversionfunctionfilter(callData){const{ arg, bySet}=callData;constargList=[];for(letitemofarg){constitemType=typeofitem;if((itemType==='number'&&!isNaN(item))||(bySet&&itemType==='string'&&item&&(item=Number(item)))){argList.push(item);}}if(argList.length||!bySet){returncallData.runApply(argList);}}wrap(api,'value',filter);api.value='some data';// value isn't changed, saveToLocalStorage isn't calledapi.value=9;// value is changed, saveToLocalStorage is calledapi.value='-53';// string is converted to number and value is changed, saveToLocalStorage is calledconstsum=wrap(api.sum,filter);constpositive=wrap(api.positive,filter);sum(false,3,NaN,newDate(),8,{},'sum','2');// Returns 11positive(true,-5,NaN,4,newDate(),1,{a:5},0,'positive',-1);// Returns [4, 1]
See additional examples in tests.
API↑
Wraps specified object's field/method or standalone function into new (wrapping) functionthat calls passed handler which eventually may run wrapped function or get/set field's value.
Arguments:
target: Function | object
- Function that should be wrapped or an object whose field/method will be wrapped and replaced.field: Function | string
- Name of field/method that should be wrapped or a handler when function is passed fortarget
parameter.handler: Function | object
- A function (interceptor) that should be executed when newly created function is called or get/set operation for the field is applied,or optional settings when function is passed fortarget
parameter.settings: object
- Optional settings that will be available inhandler
.settings.after: boolean
(optional) - Whether original function, method or field's operation should be called afterhandler
.settings.before: boolean
(optional) - Whether original function, method or field's operation should be called beforehandler
.settings.bind: boolean
(optional) - Whether wrapping function should be bound totarget
object.settings.context: object
(optional) - Context (this
) that should be used forhandler
call.settings.data: any
(optional) - Any data that should be available inhandler
.settings.get: boolean | Function
(optional) - Whether field's get operation should be interceptedand whether created wrapping function should be used as field's getter(by defaulttrue
for usual (non-functional) field andfalse
for method).settings.listen: boolean
(optional) - Whether original function, method or field's operationshould be called beforehandler
and whether original's result should be returned.settings.set: boolean | Function
(optional) - Whether field's set operation should be interceptedand whether created wrapping function should be used as field's setter(by defaulttrue
for usual (non-functional) field andfalse
for method).
Returns wrapping function whentarget
is a function,or a function that restores original field/method whentarget
is an object.
An object with the following fields will be passed intohandler
:
arg: any[]
- Array of arguments that were passed to the wrapping function.arg0: any
- Value ofarg[0]
.byCall: boolean
- Whether wrapping function is called as object's method or as usual function (by a call operation).byGet: boolean
- Whether wrapping function is called to get field's value (by get operation, as field's getter).bySet: boolean
- Whether wrapping function is called to set field's value (by set operation, as field's setter).byUnwrap: boolean
- Whether wrapping function (andhandler
) is called during unwrapping.context: object
- Context (this
) with which wrapping function is called.data: any
- Value ofsettings.data
option.field: string | undefined
- Name of the field or method that was wrapped.fieldWrap: boolean
- Whether field's get and/or set operation was wrapped.funcWrap: boolean
- Whether standalone function (not object's field/method) was wrapped.get: (() => any) | undefined
- Function that returns field's current value if field was wrapped.method: string
- Name of the method or function that was wrapped.methodWrap: boolean
- Whether method was wrapped.number: number
- Number ofhandler
's call (starting from 1).result: any
- Result of original function/method when it is called beforehandler
.run: (...args?) => any
- Method that calls original function/method or field's getter/setter;by default values fromarg
will be used as arguments;but you may pass arguments torun
and they will be used instead of the original arguments.runApply: (any[]?) => any
- Similar torun
but accepts an array of new arguments,e.g.runApply([1, 2, 3])
is equivalent torun(1, 2, 3)
;if the first argument ofrunApply
is not an array it will be wrapped into array (i.e.[arguments[0]]
);only the first argument ofrunApply
is used.save: object
- An object that can be used to preserve some values betweenhandler
calls.set: ((value: any) => any) | undefined
- Function that changes field's current value if field was wrapped.settings: object
- Value ofsettings
parameter; except forsettings.bind
andsettings.context
,it is possible to change any setting to alter following execution;so be careful when you change a field's value ofsettings
object.target: ((...args) => any) | string
- Original function or method that was wrapped, or name of wrapped field.targetObj: object | null
- An object whose field/method was wrapped and replaced.value: any
- Previous value returned by wrapping function.
Whensettings.after
andsettings.listen
arefalse
, result ofhandler
will be returned from wrapping function.
Wraps specified object's field(s)/method(s) or standalone function into new (wrapping) functionthat calls passed handler which eventually may run wrapped function or get/set field's value.
Arguments:
target: Function | object
- Function that should be wrapped or an object whose field(s)/method(s) will be wrapped and replaced.field: Function | string | string[]
- Name of field/method (or list of field/method names)that should be wrapped or a handler when function is passed fortarget
parameter.handler: Function | object
- A function (interceptor) that should be executed when newly created function is calledor get/set operation for the field is applied, or settings when function is passed fortarget
parameter.settings: object
- Optional settings that will be available inhandler
. Seewrap
for details.
Returns wrapping function whentarget
is a function,or a function that restores original field(s)/method(s) whentarget
is an object.
Seedocs
for details.
Related projects↑
Inspiration↑
This library is inspired bymeld.
Contributing↑
In lieu of a formal styleguide, take care to maintain the existing coding style.Add unit tests for any new or changed functionality.Lint and test your code.
License↑
Copyright (c) 2020 Denis Sikuler
Licensed under the MIT license.
About
Functions to wrap other functions and fields/methods and to change/enhance their behavior, functionality or usage