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
) or several fields and methods at once (by intercept
).get
option) or set operation (set
option), or both (by default).before
or listen
option),
after (use after
option) and/or inside handler
(use run()
or runApply()
).handler
:
call depending on condition, filter/validate/convert passed arguments and/or provide another arguments.handler
.handler
calls.import { intercept } from 'wrapme';
const api = {
sum(...numList) {
let result = 0;
for (let value of numList) {
result += value;
}
return result;
},
// Other methods
// ...
};
// Logging
const log = [];
function logger(callData) {
log.push({
name: callData.field,
args: callData.arg,
result: callData.result,
callNum: callData.number,
time: new Date().getTime()
});
}
const unwrap = intercept(api, 'sum', logger, {listen: true});
api.sum(1, 2, 3, 4); // Returns 10, adds item to log
api.sum(1, -1, 2, -2, 3); // Returns 3, adds item to log
// Restore original method
unwrap();
npm install wrapme
Use dist/wrapme.umd.development.js
or dist/wrapme.umd.production.min.js
(minified version).
import { intercept, wrap } from 'wrapme';
const wrapme = require('wrapme');
const { intercept, wrap } = wrapme;
define(['path/to/dist/wrapme.umd.production.min.js'], function(wrapme) {
const intercept = wrapme.intercept;
const wrap = wrapme.wrap;
});
<script type="text/javascript" src="path/to/dist/wrapme.umd.production.min.js"></script>
<script type="text/javascript">
// wrapme is available via wrapme field of window object
const intercept = wrapme.intercept;
const wrap = wrapme.wrap;
</script>
import { intercept, wrap } from 'wrapme';
const api = {
value: 1,
sum(...numList) {
let result = 0;
for (let value of numList) {
result += value;
}
return result;
},
positive(...numList) {
let result = [];
for (let value of numList) {
if (value > 0) {
result.push(value);
}
}
return result;
},
factorial(num) {
let result = 1;
while (num > 1) {
result *= num--;
}
return result;
},
binomCoeff(n, k) {
const { factorial } = api;
return factorial(n) / (factorial(k) * factorial(n - k));
}
};
// Logging
const log = [];
function logger(callData) {
if (! callData.byUnwrap) {
callData.settings.log.push({
name: callData.field,
args: callData.arg,
result: callData.result,
callNum: callData.number,
time: new Date().getTime()
});
}
}
const unwrap = intercept(api, ['sum', 'positive', 'value'], logger, {listen: true, log});
api.sum(1, 2, 3, 4); // Returns 10, adds item to log
api.positive(1, 2, -3, 0, 10, -7); // Returns [1, 2, 10], adds item to log
api.value += api.sum(1, -1, 2, -2, 3); // Changes value to 4, adds items to log
// Restore original fields
unwrap();
api.positive(-1, 5, 0, api.value, -8); // Returns [5, 4], doesn't add items to log
console.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 memoization
function memoize(callData) {
const { save } = callData;
const key = callData.arg.join(' ');
return (key in save)
? save[key]
: (save[key] = callData.run());
}
intercept(api, ['factorial', 'binomCoeff'], memoize);
api.factorial(10);
api.factorial(5);
api.binomCoeff(10, 5); // Uses already calculated factorials
api.binomCoeff(10, 5); // Uses already calculated value
// Side effects
function saveToLocalStorage(callData) {
if (callData.bySet) {
const { save } = callData;
if ('id' in save) {
clearTimeout(save.id);
}
save.id = setTimeout(
() => localStorage.setItem(
`wrap:${callData.field}`,
typeof callData.result === 'undefined'
? callData.arg0
: callData.result
),
callData.settings.timeout || 0
);
}
}
wrap(api, 'value', saveToLocalStorage, {listen: true, timeout: 50});
// Validation, filtering or conversion
function filter(callData) {
const { arg, bySet } = callData;
const argList = [];
for (let item of arg) {
const itemType = typeof item;
if ( (itemType === 'number' && ! isNaN(item))
|| (bySet && itemType === 'string' && item && (item = Number(item))) ) {
argList.push(item);
}
}
if (argList.length || ! bySet) {
return callData.runApply(argList);
}
}
wrap(api, 'value', filter);
api.value = 'some data'; // value isn't changed, saveToLocalStorage isn't called
api.value = 9; // value is changed, saveToLocalStorage is called
api.value = '-53'; // string is converted to number and value is changed, saveToLocalStorage is called
const sum = wrap(api.sum, filter);
const positive = wrap(api.positive, filter);
sum(false, 3, NaN, new Date(), 8, {}, 'sum', '2'); // Returns 11
positive(true, -5, NaN, 4, new Date(), 1, {a: 5}, 0, 'positive', -1); // Returns [4, 1]
See additional examples in tests.
Wraps specified object's field/method or standalone function into new (wrapping) function that 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 for target
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 for target
parameter.settings: object
- Optional settings that will be available in handler
.settings.after: boolean
(optional) - Whether original function, method or field's operation should be called after handler
.settings.before: boolean
(optional) - Whether original function, method or field's operation should be called before handler
.settings.bind: boolean
(optional) - Whether wrapping function should be bound to target
object.settings.context: object
(optional) - Context (this
) that should be used for handler
call.settings.data: any
(optional) - Any data that should be available in handler
.settings.get: boolean | Function
(optional) - Whether field's get operation should be intercepted
and whether created wrapping function should be used as field's getter
(by default true
for usual (non-functional) field and false
for method).settings.listen: boolean
(optional) - Whether original function, method or field's operation
should be called before handler
and whether original's result should be returned.settings.set: boolean | Function
(optional) - Whether field's set operation should be intercepted
and whether created wrapping function should be used as field's setter
(by default true
for usual (non-functional) field and false
for method).Returns wrapping function when target
is a function,
or a function that restores original field/method when target
is an object.
An object with the following fields will be passed into handler
:
arg: any[]
- Array of arguments that were passed to the wrapping function.arg0: any
- Value of arg[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 (and handler
) is called during unwrapping.context: object
- Context (this
) with which wrapping function is called.data: any
- Value of settings.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 of handler
's call (starting from 1).result: any
- Result of original function/method when it is called before handler
.run: (...args?) => any
- Method that calls original function/method or field's getter/setter;
by default values from arg
will be used as arguments;
but you may pass arguments to run
and they will be used instead of the original arguments.runApply: (any[]?) => any
- Similar to run
but accepts an array of new arguments,
e.g. runApply([1, 2, 3])
is equivalent to run(1, 2, 3)
;
if the first argument of runApply
is not an array it will be wrapped into array (i.e. [arguments[0]]
);
only the first argument of runApply
is used.save: object
- An object that can be used to preserve some values between handler
calls.set: ((value: any) => any) | undefined
- Function that changes field's current value if field was wrapped.settings: object
- Value of settings
parameter; except for settings.bind
and settings.context
,
it is possible to change any setting to alter following execution;
so be careful when you change a field's value of settings
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.When settings.after
and settings.listen
are false
, result of handler
will be returned from wrapping function.
Wraps specified object's field(s)/method(s) or standalone function into new (wrapping) function that 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 for target
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 settings when function is passed for target
parameter.settings: object
- Optional settings that will be available in handler
. See wrap
for details.Returns wrapping function when target
is a function,
or a function that restores original field(s)/method(s) when target
is an object.
See docs
for details.
This library is inspired by meld.
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.
Copyright (c) 2020 Denis Sikuler
Licensed under the MIT license.
Generated using TypeDoc