typescript-class-helpers
Version:
Usefull helper to have in your typescript project
625 lines (610 loc) • 22.2 kB
JavaScript
import { _, Helpers } from 'tnp-core/browser';
export * from 'enum-values';
var Models;
(function (Models) {
/**
* @deprecated
*/
class ParamConfig {
}
Models.ParamConfig = ParamConfig;
/**
* @deprecated
*/
class MethodConfig {
constructor() {
/* */
/* */
this.parameters = {};
}
}
Models.MethodConfig = MethodConfig;
/**
* @deprecated
*/
class ClassConfig {
constructor() {
// @ts-ignore
this.singleton = void 0;
this.injections = [];
this.vChildren = [];
this.methods = {};
}
}
Models.ClassConfig = ClassConfig;
})(Models || (Models = {}));
const SYMBOL = {
MODELS_MAPPING: Symbol(),
DEFAULT_MODEL: Symbol(),
STORAGE: 'classesstorage',
CLASSES: 'classes',
ClassNameStaticProperty: '$$className$$'
};
function defaultValues() {
return _.cloneDeep({
[SYMBOL.CLASSES]: []
});
}
function getStorage(property) {
/* */
/* */
/* */
/* */
if (typeof property === 'string') {
const storageInClassStaticProp = getStorage();
return storageInClassStaticProp[property];
}
if (typeof defaultValues.prototype[SYMBOL.STORAGE] === 'undefined') {
defaultValues.prototype[SYMBOL.STORAGE] = defaultValues();
}
return defaultValues.prototype[SYMBOL.STORAGE];
}
function getClasses$1() {
const s = getStorage();
return s[SYMBOL.CLASSES];
}
// @ts-ignore
function setClassName(target, className, options) {
let { classFamily, uniqueKey, classNameInBrowser, singleton } = options || {
classFamily: void 0,
uniqueKey: 'id',
classNameInBrowser: void 0,
singleton: void 0,
autoinstance: false
};
if (!_.isUndefined(singleton) && _.isBoolean(singleton) && singleton) {
singleton = 'first-instance';
}
if (!uniqueKey) {
uniqueKey = 'id';
}
if (target) {
const config = _.first(CLASSNAME.getClassConfig(target));
config.className = className;
config.uniqueKey = uniqueKey;
config.classNameInBrowser = classNameInBrowser;
// console.log(`Setting class Name to "${target.name}"`)
}
const existed = getClasses$1()
.find(f => f.className === className);
if (existed) {
existed.target = target;
}
else {
const res = {
className,
classNameInBrowser,
target,
uniqueKey,
classFamily
};
if (_.isUndefined(classFamily)) {
Object.defineProperty(res, 'classFamily', {
get: function () {
const parent = Object.getPrototypeOf(target);
if (!_.isFunction(parent) || parent.name === 'Object' || parent.name === '') {
return className;
}
const classNameNew = CLASSNAME.getClassName(parent);
return CLASSNAME.getClassFamilyByClassName(classNameNew);
}
});
}
getClasses$1().push(res);
}
const Original = target;
if (singleton === 'first-instance' || singleton === 'last-instance') {
const obj = {
// @ts-ignore
decoratedConstructor: function (...args) {
// console.log(`DECORATED CONSTRUCTOR OF ${Original.name}`)
const context = Original.apply(this, args);
const existedSingleton = CLASS.getSingleton(Original);
if (!existedSingleton || singleton === 'last-instance') {
CLASS.setSingletonObj(Original, this);
CLASS.setSingletonObj(obj.decoratedConstructor, this);
// console.log(`Singleton created for "${className}", mode: ${singleton} `);
}
else {
// console.log('ingleton exists')
}
return context;
}
};
// copy prototype so intanceof operator still works
obj.decoratedConstructor.prototype = Original.prototype;
// @ts-ignore
Object.keys(Original).forEach((name) => { obj.decoratedConstructor[name] = Original[name]; });
Object.defineProperty(obj.decoratedConstructor, 'name', {
value: className,
configurable: true,
});
// (obj.decoratedConstructor as any).name = className;
// console.log('return new contruor', decoratedConstructor)
return obj.decoratedConstructor;
}
else if (singleton === 'autoinstance') {
// console.log(`AUTOINSTANCE FOR ${target.name}`)
const auto = new Original();
CLASS.setSingletonObj(Original, auto);
// console.log(`Singleton created for "${className}", mode: ${singleton} `)
}
}
const registerd = {
configs: [],
classes: [],
};
const ERROR_MSG_CLASS_WITHOUT_DECORATOR = `[typescript-class-helpers][getClassName(...)](PRODUCTION MODE ERROR)
Please use decoartor @CLASSNAME for each entity or controller
This is preventing class name problem in minified code.
import { CLASS } from 'typescript-class-helpers';
@CLASS.NAME('ExampleClass')
class ExampleClass {
...
}
`;
/* */
/* */
function getClasses() {
const s = getStorage();
return s[SYMBOL.CLASSES];
}
var CLASSNAME;
(function (CLASSNAME_1) {
function getClassConfig(target, configs = [], callerTarget) {
if (!_.isFunction(target)) {
throw `[typescript-class-helper][getClassConfig] Cannot get class config from: ${target}`;
}
let config;
const parentClass = Object.getPrototypeOf(target);
const isValidParent = _.isFunction(parentClass) && parentClass.name !== '';
if (registerd.classes.includes(target)) {
config =
registerd.configs[registerd.classes.findIndex(c => c === target)];
}
else {
config = new Models.ClassConfig();
config.classReference = target;
registerd.classes.push(target);
}
registerd.configs[registerd.classes.findIndex(c => c === target)] = config;
if (callerTarget) {
const callerTargetConfig = registerd.configs[registerd.classes.findIndex(c => c === callerTarget)];
if (!config.vChildren.includes(callerTargetConfig)) {
config.vChildren.push(callerTargetConfig);
}
callerTargetConfig.vParent = config;
}
configs.push(config);
return isValidParent
? getClassConfig(parentClass, configs, target)
: configs;
}
CLASSNAME_1.getClassConfig = getClassConfig;
/**
* PLEASE PROVIDE NAME AS TYPED STRING, NOT VARIABLE
* Decorator requred for production mode
* @param name Name of class
*/
function CLASSNAME(className, options) {
return function (target) {
// console.log(`CLASSNAME Inited ${className}`)
return setClassName(target, className, options);
};
}
CLASSNAME_1.CLASSNAME = CLASSNAME;
function getClassName(target, production = false) {
if (_.isNil(target)) {
// console.log(target);
// Helpers.warn(`[typescript-class-helpers][getClassName] target is nil`)
return void 0;
}
if (_.isString(target)) {
// console.log(target);
Helpers.log(`[typescript-class-helpers][getClassName] target is string: '${target}', produciton: ${production}`);
return target;
}
if (target === Date) {
return 'Date';
}
if (target === FormData) {
return 'FormData';
}
if (!_.isFunction(target)) {
// console.log(target);
Helpers.log(`[typescript-class-helpers][getClassName] target is not a class`);
return void 0;
}
if (target[SYMBOL.ClassNameStaticProperty]) {
return target[SYMBOL.ClassNameStaticProperty];
}
const configs = getClassConfig(target);
const config = _.first(configs);
const classNameInBrowser = config?.classNameInBrowser;
if (Helpers.isBrowser && _.isString(classNameInBrowser)) {
return classNameInBrowser;
}
const className = config?.className;
if (typeof className === 'string') {
return className;
}
if (production) {
console.log('class without @CLASS.NAME deocrator', target);
throw new Error(ERROR_MSG_CLASS_WITHOUT_DECORATOR);
}
else {
// Helpers.log('check for ' + target.name)
// setTimeout(() => {
// // Helpers.log('check for ' + target.name + ' === ' + config.className)/
// // TODO this may work, but not yet in singleton/morphi
// if (!config.className) {
// if (target?.name && target.name !== 'Object') {
// ConfigHelpers.log(`[typescript-class-helpers] Please use @CLASS.NAME`
// + `('${(target?.name && !!target.name) ? target.name : '...'}') decorator for class ${target?.name}`)
// }
// }
// })
}
// special thing when cloning classes
if (target.name?.startsWith('class_')) {
return '';
}
return target.name;
}
CLASSNAME_1.getClassName = getClassName;
// @ts-ignore
function getObjectIndexPropertyValue(obj) {
const className = TchHelpers.getNameFromObject(obj);
// console.log('className',className)
let c = getClasses().find(c => c.className === className);
// console.log('c',c)
if (c) {
return c.uniqueKey;
}
}
CLASSNAME_1.getObjectIndexPropertyValue = getObjectIndexPropertyValue;
// @ts-ignore
function getClassFamilyByClassName(className) {
let c = getClasses().find(c => c.className === className);
// console.log('getClasses()', getClasses())
if (c) {
return c.classFamily;
}
}
CLASSNAME_1.getClassFamilyByClassName = getClassFamilyByClassName;
// @ts-ignore
function getObjectClassFamily(obj) {
const className = TchHelpers.getNameFromObject(obj);
// console.log('className',className)
let c = getClasses().find(c => c.className === className);
// console.log('c',c)
if (c) {
return c.classFamily;
}
}
CLASSNAME_1.getObjectClassFamily = getObjectClassFamily;
function getObjectIndexValue(obj) {
const className = TchHelpers.getNameFromObject(obj);
// console.log('className',className)
let c = getClasses().find(c => c.className === className);
// console.log('c',c)
if (c && _.isString(c.uniqueKey)) {
return obj[c.uniqueKey];
}
}
CLASSNAME_1.getObjectIndexValue = getObjectIndexValue;
function getClassBy(className) {
let res;
if (Array.isArray(className)) {
if (className.length !== 1) {
throw `Mapping error... please use proper class names:
{
propertyObject: 'MyClassName',
propertyWithArray: ['MyClassName']
}
`;
}
className = className[0];
}
if (typeof className === 'function') {
// TODO QUICK_FIX
res = className;
}
if (className === 'Date') {
res = Date;
}
if (className === 'FormData') {
res = FormData;
}
let c = getClasses().find(c => c.className === className);
if (c) {
res = c.target;
}
// console.log(`getClassBy "${className} return \n\n ${res} \n\n`)
// @ts-ignore
return res;
}
CLASSNAME_1.getClassBy = getClassBy;
})(CLASSNAME || (CLASSNAME = {}));
/**
* @DEPRECATED
* Describe fields assigned in class
*/
const FRegEx = new RegExp(/(?:this\.)(.+?(?= ))/g);
function describeFromClassStringify(target, parent = false) {
// @ts-ignore
var result = [];
if (parent) {
var proto = Object.getPrototypeOf(target.prototype);
if (proto) {
// @ts-ignore
result = result.concat(describeFromClassStringify(proto.constructor, parent));
}
}
const classString = target.toString();
const matches = classString.match(FRegEx) || [];
// console.log({
// classString,
// });
result = result.concat(matches);
return result.map(prop => prop
.replace('this.', '')
.replace('.', '')
.replace(')', '')
.replace('(', ''));
}
/**
* Describe fields assigne through @DefaultModelWithMapping decorator
* without functions
*/
function describeByDefaultModelsAndMapping(target) {
let res = {};
if (target) {
// @ts-ignore
if (target[SYMBOL.DEFAULT_MODEL]) {
// @ts-ignore
Object.keys(target[SYMBOL.DEFAULT_MODEL])
.filter(key => {
// @ts-ignore
return !_.isFunction(target[SYMBOL.DEFAULT_MODEL][key]);
})
.forEach(key => {
// @ts-ignore
return res[key] = null;
});
}
// @ts-ignore
let mapping = target[SYMBOL.MODELS_MAPPING];
if (_.isArray(mapping)) {
mapping.forEach(m => {
Object.keys(m)
.forEach(key => {
// @ts-ignore
res[key] = null;
});
});
}
else if (mapping) {
Object.keys(mapping)
.forEach(key => {
// @ts-ignore
res[key] = null;
});
}
}
let propNames = Object.keys(res).filter(f => !!f);
propNames = (!propNames.includes('id') ? ['id'] : []).concat(propNames);
return propNames;
}
class TchHelpers {
static getBy(className) {
return CLASSNAME.getClassBy(className);
}
static getFromObject(o) {
if (_.isUndefined(o) || _.isNull(o)) {
return;
}
if (o.constructor) {
return o.constructor;
}
const p = Object.getPrototypeOf(o);
return p && p.constructor;
}
static getName(target, production = false) {
return CLASSNAME.getClassName(target, production);
}
static getNameFromObject(o) {
const fnCLass = this.getFromObject(o);
return this.getName(fnCLass);
}
static getConfigs(target) {
return CLASSNAME.getClassConfig(target);
}
static describeProperites(target) {
const d1 = describeFromClassStringify(target);
const d2 = describeByDefaultModelsAndMapping(target);
let uniq = {};
// @ts-ignore
d1.concat(d2).forEach(p => uniq[p] = p);
return Object.keys(uniq)
.filter(d => !!d)
.filter(d => typeof target.prototype[d] !== 'function');
}
}
const notAllowedAsMethodName = [
'length', 'name',
'arguments', 'caller',
'constructor', 'apply',
'bind', 'call',
'toString',
'__defineGetter__',
'__defineSetter__', 'hasOwnProperty',
'__lookupGetter__', '__lookupSetter__',
'isPrototypeOf', 'propertyIsEnumerable',
'valueOf', '__proto__', 'toLocaleString',
];
const getMethodsNames = (classOrClassInstance, allMethodsNames = []) => {
if (!classOrClassInstance) {
return allMethodsNames;
}
const isClassFunction = _.isFunction(classOrClassInstance);
const classFun = (isClassFunction ? classOrClassInstance : Object.getPrototypeOf(classOrClassInstance));
const objectToCheck = isClassFunction ? classOrClassInstance?.prototype : classOrClassInstance;
const prototypeObj = Object.getPrototypeOf(objectToCheck || {});
const properties = _.uniq([
...Object.getOwnPropertyNames(objectToCheck || {}),
...Object.getOwnPropertyNames(prototypeObj || {}),
...Object.keys(objectToCheck || {}),
...Object.keys(prototypeObj || {}),
])
.filter(f => !!f && !notAllowedAsMethodName.includes(f));
properties.filter((methodName) => typeof objectToCheck[methodName] === 'function').forEach(p => allMethodsNames.push(p));
if (!classFun || !classFun.constructor || classFun?.constructor?.name === 'Object') {
return allMethodsNames;
}
return getMethodsNames(Object.getPrototypeOf(classFun), allMethodsNames);
};
const CLASS = {
NAME: CLASSNAME.CLASSNAME,
setName: setClassName,
getBy: TchHelpers.getBy,
/**
* @deprecated
*/
getSingleton(target) {
if (typeof target !== 'function') {
console.error(`[typescript-class-helpers][setSingletonObj] Type of target is not a function`);
return;
}
const config = TchHelpers.getConfigs(target)[0];
// console.log(`getSingleton for ${target.name}: `, config.singleton)
return config.singleton;
},
/**
* @deprecated
*/
setSingletonObj(target, singletonObject) {
// console.log('SET SINGLETON', singletonObject)
if (typeof target !== 'function') {
console.error(`[typescript-class-helpers][setSingletonObj] Type of target is not a function`);
return;
}
if (Array.isArray(singletonObject)) {
console.error(`[typescript-class-helpers][setSingletonObj] Singleton instance cant be an array`);
return;
}
if (typeof singletonObject !== 'object') {
console.error(`[typescript-class-helpers][setSingletonObj] Singleton instance cant must be a object`);
return;
}
const className = CLASS.getName(target);
// console.log(`SINGLETON SET for TARGET ${className}`)
const config = TchHelpers.getConfigs(target)[0];
if (config.singleton) {
console.warn(`[typescript-class-helpers] You are trying to set singleton of "${className}" second time with different object.`);
}
config.singleton = singletonObject;
},
/**
* @deprecated
*/
getConfigs: TchHelpers.getConfigs,
/**
* @deprecated
*/
getConfig: (target) => {
return _.first(TchHelpers.getConfigs(target));
},
getMethodsNames(classOrClassInstance) {
return getMethodsNames(classOrClassInstance);
},
getFromObject: TchHelpers.getFromObject,
getName: TchHelpers.getName,
getNameFromObject: TchHelpers.getNameFromObject,
describeProperites: TchHelpers.describeProperites,
/**
* @deprecated
*/
OBJECT: (obj) => {
return {
get indexValue() {
return CLASSNAME.getObjectIndexValue(obj);
},
get indexProperty() {
return CLASSNAME.getObjectIndexPropertyValue(obj);
},
get isClassObject() {
if (!_.isObject(obj) || _.isArray(obj) || _.isRegExp(obj) ||
_.isBuffer(obj) || _.isArrayBuffer(obj)) {
return false;
}
if (_.isDate(obj)) {
return true;
}
const className = CLASS.getNameFromObject(obj);
return _.isString(className) && className !== 'Object';
},
isEqual: (anotherObj, compareDeep = false) => {
if (!CLASS.OBJECT(obj).isClassObject || !CLASS.OBJECT(anotherObj).isClassObject) {
return false;
}
if (obj === anotherObj) {
// console.log(`EQ v1: , v2: - class1 ${CLASS.getNameFromObject(obj)}, class2 ${CLASS.getNameFromObject(anotherObj)}`, obj, anotherObj)
return true;
}
const v1 = CLASSNAME.getObjectIndexValue(obj);
const v2 = CLASSNAME.getObjectIndexValue(anotherObj);
const f1 = CLASSNAME.getObjectClassFamily(obj);
const f2 = CLASSNAME.getObjectClassFamily(anotherObj);
const isFamilyDiff = (!_.isString(f1) || !_.isString(f2) || (f1 !== f2));
// console.log(`DIFF FAMILY ${isFamilyDiff} v1: ${CLASSNAME.getObjectClassFamily(obj)}, v2: ${CLASSNAME.getObjectClassFamily(anotherObj)} - class1 ${CLASS.getNameFromObject(obj)}, class2 ${CLASS.getNameFromObject(anotherObj)}`)
if (isFamilyDiff) {
// console.log(`DIFF v1: ${v1}, v2: ${v2} - class1 ${CLASS.getNameFromObject(obj)}, class2 ${CLASS.getNameFromObject(anotherObj)}`)
return false;
}
if (!CLASS.OBJECT(obj).isClassObject || !CLASS.OBJECT(anotherObj).isClassObject) {
// console.log(`NOT CLASS v1: ${v1}, v2: ${v2} - class1 ${CLASS.getNameFromObject(obj)}, class2 ${CLASS.getNameFromObject(anotherObj)}`)
return false;
}
// console.log(`v1: ${v1} ,class ${CLASS.getNameFromObject(obj)} ,prop: ${CLASS.OBJECT(obj).indexProperty}`)
// console.log(`v2: ${v2} ,class ${CLASS.getNameFromObject(anotherObj)} ,prop: ${CLASS.OBJECT(anotherObj).indexProperty}`)
// console.log(`v1: ${v1}, v2: ${v2} - class1 ${CLASS.getNameFromObject(obj)}, class2 ${CLASS.getNameFromObject(anotherObj)}`)
// console.log('')
if ((_.isNumber(v1) && _.isNumber(v2)) || (_.isString(v1) && _.isString(v2))) {
const res = (v1 === v2);
// @ts-ignore
return res;
}
if (compareDeep) {
return _.isEqual(v1, v2);
}
return false;
}
};
}
};
/**
* Generated bundle index. Do not edit.
*/
export { CLASS, Models, SYMBOL, TchHelpers };
//# sourceMappingURL=typescript-class-helpers.mjs.map