nativescript
Version:
Command-line interface for building NativeScript projects
237 lines • 9.54 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.cache = cache;
exports.memoize = memoize;
exports.invokeBefore = invokeBefore;
exports.invokeInit = invokeInit;
exports.exported = exported;
exports.performanceLog = performanceLog;
exports.deprecated = deprecated;
const constants_1 = require("../constants");
const yok_1 = require("./yok");
/**
* Caches the result of the first execution of the method and returns it whenever it is called instead of executing it again.
* Works with methods and getters.
* @example
* ```
* class CacheDecoratorsTest {
*
* @cache()
* public method(num: number): number {
* return num;
* }
*
* @cache()
* public get property(): any {
* // execute some heavy operation.
* return result;
* }
* }
*
* const instance = new CacheDecoratorsTest();
* const result = instance.method(1); // returns 1;
*
* // all consecutive calls to instance.method will return 1.
* const result2 = instance.method(2); // returns 1;
* ```
*/
function cache() {
return (target, propertyKey, descriptor) => {
let result;
const propName = descriptor.value ? "value" : "get";
const originalValue = descriptor[propName];
descriptor[propName] = function (...args) {
const propertyName = `__isCalled_${propertyKey}__`;
if (this && !this[propertyName]) {
this[propertyName] = true;
result = originalValue.apply(this, args);
}
return result;
};
return descriptor;
};
}
let memoizeIDCounter = 0;
function memoize(options) {
return (target, propertyKey, descriptor) => {
// todo: remove once surely working as intended.
const DEBUG = false;
const memoizeID = memoizeIDCounter++;
const valueOrGet = descriptor.value ? "value" : "get";
const originalMethod = descriptor[valueOrGet];
descriptor[valueOrGet] = function (...args) {
const cacheMapName = `__memoize_cache_map_${memoizeID}`;
DEBUG && console.log(options);
let hashKey;
if (options.hashFn) {
DEBUG && console.log({ args });
hashKey = options.hashFn.apply(this, args);
}
else {
hashKey = `__memoize_cache_value_${memoizeID}`;
}
DEBUG &&
console.log({
cacheMapName,
hashKey,
});
// initialize cache map if not exists
if (!this.hasOwnProperty(cacheMapName)) {
DEBUG && console.log("NO CACHE MAP YET, CREATING ONE NOW");
Object.defineProperty(this, cacheMapName, {
configurable: false,
enumerable: false,
writable: false,
value: new Map(),
});
}
const cacheMap = this[cacheMapName];
DEBUG &&
console.log({
cacheMap,
});
// check if has memoized value based on hashFn
if (cacheMap.has(hashKey)) {
DEBUG && console.log("CACHE HIT");
// if yes, return cached value
return cacheMap.get(hashKey);
}
DEBUG && console.log("CACHE MISS");
// if not call original and get result
const result = originalMethod.apply(this, args);
// call shouldCache (if passed) with the result or default to true
let shouldCache = true;
if (options.shouldCache) {
shouldCache = options.shouldCache.call(this, result);
}
DEBUG && console.log("GOT BACK SHOULDCACHE", shouldCache);
if (shouldCache) {
DEBUG && console.log("CACHING NOW");
cacheMap.set(hashKey, result);
}
// if shouldCache: save result
DEBUG && console.log("RETURNING", result);
return result;
};
return descriptor;
};
}
/**
* Calls specific method of the instance before executing the decorated method.
* This is usable when some of your methods depend on initialize async method, that cannot be invoked in constructor of the class.
* IMPORTANT: The decorated method must be async.
* @param {string} methodName The name of the method that will be invoked before calling the decorated method.
* @param {any[]} methodArgs Args that will be passed to the method that will be invoked before calling the decorated one.
* @return {any} Result of the decorated method.
*/
function invokeBefore(methodName, methodArgs) {
return (target, propertyKey, descriptor) => {
const originalValue = descriptor.value;
descriptor.value = async function (...args) {
await target[methodName].apply(this, methodArgs);
return originalValue.apply(this, args);
};
return descriptor;
};
}
function invokeInit() {
return invokeBefore("init");
}
function exported(moduleName) {
return (target, propertyKey, descriptor) => {
yok_1.injector.publicApi.__modules__[moduleName] =
yok_1.injector.publicApi.__modules__[moduleName] || {};
yok_1.injector.publicApi.__modules__[moduleName][propertyKey] = (...args) => {
const originalModule = yok_1.injector.resolve(moduleName), originalMethod = originalModule[propertyKey], result = originalMethod.apply(originalModule, args);
return result;
};
return descriptor;
};
}
function performanceLog(localInjector) {
localInjector = localInjector || yok_1.injector;
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
const className = target.constructor.name;
const trackName = `${className}${constants_1.AnalyticsEventLabelDelimiter}${propertyKey}`;
const performanceService = localInjector.resolve("performanceService");
//needed for the returned function to have the same name as the original - used in hooks decorator
const functionWrapper = {
[originalMethod.name]: function (...args) {
const start = performanceService.now();
const result = originalMethod.apply(this, args);
const resolvedPromise = Promise.resolve(result);
let end;
if (resolvedPromise !== result) {
end = performanceService.now();
performanceService.processExecutionData(trackName, start, end, args);
}
else {
resolvedPromise
.then(() => {
end = performanceService.now();
performanceService.processExecutionData(trackName, start, end, args);
})
.catch((err) => {
end = performanceService.now();
performanceService.processExecutionData(trackName, start, end, args);
});
}
return result;
},
};
descriptor.value = functionWrapper[originalMethod.name];
// used to get parameter names in hooks decorator
descriptor.value.toString = () => {
return originalMethod.toString();
};
return descriptor;
};
}
// inspired by https://github.com/NativeScript/NativeScript/blob/55dfe25938569edbec89255008e5ad9804901305/tns-core-modules/globals/globals.ts#L121-L137
function deprecated(additionalInfo, localInjector) {
const isDeprecatedMessage = " is deprecated.";
return (target, key, descriptor) => {
localInjector = localInjector || yok_1.injector;
additionalInfo = additionalInfo || "";
const $logger = localInjector.resolve("logger");
if (descriptor) {
if (descriptor.value) {
// method
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
$logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
else {
// property
if (descriptor.set) {
const originalSetter = descriptor.set;
descriptor.set = function (...args) {
$logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
originalSetter.apply(this, args);
};
}
if (descriptor.get) {
const originalGetter = descriptor.get;
descriptor.get = function (...args) {
$logger.warn(`${key.toString()}${isDeprecatedMessage} ${additionalInfo}`);
return originalGetter.apply(this, args);
};
}
return descriptor;
}
}
else {
// class
$logger.warn(`${(target &&
(target.name ||
(target.constructor && target.constructor.name))) ||
target}${isDeprecatedMessage} ${additionalInfo}`);
return target;
}
};
}
//# sourceMappingURL=decorators.js.map
;