export type Constructor = new (...args: any[]) => T; export type Mixin = Constructor | object; function mix(client: Constructor, mixins: Mixin[]) { const clientKeys = Object.getOwnPropertyNames( client.prototype ); for (let mixin of mixins) { const mixinMixables = getMixables(clientKeys, mixin); Object.defineProperties( client.prototype, mixinMixables ); } } /** * Returns a map of mixables. That is things that can be mixed in */ function getMixables(clientKeys:string[], mixin: Mixin) { let descriptors:PropertyDescriptorMap = {}; switch (typeof mixin) { case "object": descriptors = getMixables(mixin); break; case "function": descriptors = getMixables((mixin as Constructor).prototype); break; } return descriptors; function getMixables(obj:object):PropertyDescriptorMap { const map:PropertyDescriptorMap = {}; Object.getOwnPropertyNames( obj ).map( key => { if( clientKeys.indexOf( key ) < 0 ) { const descriptor = Object.getOwnPropertyDescriptor( obj, key ); if( descriptor === undefined ) return if( descriptor.get || descriptor.set ) { map[ key ] = descriptor; } else if ( typeof descriptor.value === "function" ) { map[ key ] = descriptor; } } }) return map; } } /** * Takes a list of classes or object literals and adds their methods * to the class calling it. */ export function use(...options: Mixin[]) { return function (target: any, propertyKey: string) { mix(target.constructor, options.reverse()); } } /** * Takes a method as a parameter and add it to the class calling it. */ export function delegate(method: (...args: any[]) => any) { return function (target: any, propertyKey: string) { target.constructor.prototype[propertyKey] = method; } }