import { IInstance } from './interfaces'; import { ConstructClassWithPrivateMembers } from '../helpers/ClassWithPrivateMembers'; import { GetPropertyDescriptor } from '../helpers/object-helpers'; // const propertiesFunctionMap: WeakMap> = new WeakMap>(); export function GetOrCreatePropertiesFunctionMap(instance: object): Map { if (propertiesFunctionMap.has(instance)) { return propertiesFunctionMap.get(instance) as Map; } else { const map: Map = new Map(); propertiesFunctionMap.set(instance, map); return map; } } export function GetOrCreateInstanceFunctionProperty any>(instance: object, propertyKey: PropertyKey, callback: T, functionsMap: Map = GetOrCreatePropertiesFunctionMap(instance)): T { if (functionsMap.has(propertyKey)) { return functionsMap.get(propertyKey) as T; } else { const _callback = callback.bind(instance); functionsMap.set(propertyKey, _callback); return _callback; } } /*----------------------------*/ /** PRIVATES **/ export const INSTANCE_PRIVATE = Symbol('instance-private'); export interface IInstancePrivate { instance: TInstance; proto: TPrototype; functionsMap: Map; } export interface IInstanceInternal extends IInstance { [INSTANCE_PRIVATE]: IInstancePrivate; } /** CONSTRUCTOR **/ export function ConstructInstance( instance: IInstance, _instance: TInstance, proto: TPrototype = instance.constructor as TPrototype, ): void { ConstructClassWithPrivateMembers(instance, INSTANCE_PRIVATE); const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; privates.instance = _instance; privates.proto = proto; privates.functionsMap = GetOrCreatePropertiesFunctionMap(privates.instance); } /** FUNCTIONS **/ export function InstanceGetPropertyDescriptor(instance: IInstance, propertyKey: PropertyKey): PropertyDescriptor | undefined { return GetPropertyDescriptor((instance as IInstanceInternal)[INSTANCE_PRIVATE].proto, propertyKey); } /** METHODS **/ export function InstanceProp(instance: IInstance, propertyKey: PropertyKey): T | undefined { const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; const descriptor: PropertyDescriptor | undefined = InstanceGetPropertyDescriptor(instance, propertyKey); if (descriptor === void 0) { return void 0; } else { if (typeof descriptor.get === 'function') { return descriptor.get.call(privates.instance); } else if (typeof descriptor.set === 'function') { throw new Error(`The property ${ String(propertyKey) } is a pure setter (not gettable)`); } else if (typeof descriptor.value === 'function') { return GetOrCreateInstanceFunctionProperty(privates.instance, propertyKey, descriptor.value, privates.functionsMap) as T; } else { return descriptor.value; } } } export function InstanceAssign(instance: IInstance, propertyKey: PropertyKey, value: any): void { const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; const descriptor: PropertyDescriptor | undefined = InstanceGetPropertyDescriptor(instance, propertyKey); if ((descriptor !== void 0) && (typeof descriptor.set === 'function')) { return descriptor.set.call(privates.instance, value); } else { (privates.instance as any)[propertyKey] = value; } } export function InstanceGet(instance: IInstance, propertyKey: PropertyKey): T { const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; const descriptor: PropertyDescriptor | undefined = InstanceGetPropertyDescriptor(instance, propertyKey); if ((descriptor !== void 0) && (typeof descriptor.get === 'function')) { return descriptor.get.call(privates.instance); } else { throw new Error(`The property ${ String(propertyKey) } is not a getter`); } } export function InstanceSet(instance: IInstance, propertyKey: PropertyKey, value: any): void { const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; const descriptor: PropertyDescriptor | undefined = InstanceGetPropertyDescriptor(instance, propertyKey); if ((descriptor !== void 0) && (typeof descriptor.set === 'function')) { return descriptor.set.call(privates.instance, value); } else { throw new Error(`The property ${ String(propertyKey) } is not a setter`); } } export function InstanceApply(instance: IInstance, propertyKey: PropertyKey, args: any[] = []): T { const privates: IInstancePrivate = (instance as IInstanceInternal)[INSTANCE_PRIVATE]; const descriptor: PropertyDescriptor | undefined = InstanceGetPropertyDescriptor(instance, propertyKey); if ((descriptor !== void 0) && (typeof descriptor.value === 'function')) { return descriptor.value.apply(privates.instance, args); } else { throw new Error(`The property ${ String(propertyKey) } is not a function`); } } /** CLASS **/ export class Instance implements IInstance { constructor(instance: TInstance, proto?: TPrototype) { ConstructInstance(this, instance, proto); } get instance(): TInstance { return ((this as unknown) as IInstanceInternal)[INSTANCE_PRIVATE].instance; } get proto(): TPrototype { return ((this as unknown) as IInstanceInternal)[INSTANCE_PRIVATE].proto; } prop(propertyKey: PropertyKey): T | undefined { return InstanceProp(this, propertyKey); } assign(propertyKey: PropertyKey, value: any): void { return InstanceAssign(this, propertyKey, value); } call(propertyKey: PropertyKey, ...args: any[]): T { return this.apply(propertyKey, args); } get(propertyKey: PropertyKey): T { return InstanceGet(this, propertyKey); } set(propertyKey: PropertyKey, value: any): void { return InstanceSet(this, propertyKey, value); } apply(propertyKey: PropertyKey, args?: any[]): T { return InstanceApply(this, propertyKey, args); } }