UNPKG

foibles

Version:

Composition and mixins and TypeScript classes

106 lines (95 loc) 2.83 kB
/** * Any function that returns `A`. */ export type AnyFunction<A> = (...input: any[]) => A; /** * Any constructor that creates `A`. */ export type AnyConstructor<A> = new (...input: any[]) => A; /** * Type generator for mixins. Use together with the result of `toMixin` to * create a useable type: * * ```typescript * type ExampleMixin = Mixin<typeof ExampleMixin>; * const ExampleMixin = toMixin((base: object) => class extends base { * ... * }); * ``` */ export type Mixin<T extends AnyFunction<any>> = InstanceType<ReturnType<T>>; /** * Function that mixes in functionality on top of a class. Will receive the * base class to be used and must return an extended class. */ export type MixinFunction<T extends AnyConstructor<any>, O extends AnyConstructor<any>> = (base: T) => O; // Symbol used to mark if a certain mixin has been applied const application = Symbol('decorator:application'); /** * Check if the given prototype has the given mixin applied to itself. * * @param object * @param mixin */ function has(object: any, mixin: any) { while(object != null) { if(object.hasOwnProperty(application) && object[application] === mixin) { return true; } object = Object.getPrototypeOf(object); } } /** * Turn a function into a fully featured mixin. The mixin function should be * a function which takes a single argument which is the class to extend and * the function must return an extended class. * * ```javascript * const ExampleMixin = toMixin(base => class extends base { * ... * }); * ``` * * @param func */ export function toMixin<T extends AnyConstructor<any>, O extends AnyConstructor<any>>(func: MixinFunction<T, O>): MixinFunction<T, O> { /* * Define a function that checks if the mixin has already been applied * to the prototype chain. This allows mixins to use other mixins as needed * without them being applied twice. */ const mixinOnce = function(base: T): O { if(has(base.prototype, func)) return base as any; const result = func(base); result.prototype[application] = func; return result; }; /* * Define a custom hasInstance symbol so that instanceof can be used if * the environment supports it. */ if(Symbol.hasInstance) { if(func.hasOwnProperty(Symbol.hasInstance)) { /* * Mixin has its own hasInstance, let the outer function delegate * to it. */ Object.defineProperty(mixinOnce, Symbol.hasInstance, { value: function mixinHasInstance(other: any) { return func[Symbol.hasInstance](other); } }); } else { /* * The mixin function does not have a custom hasInstance, use our * own. */ Object.defineProperty(mixinOnce, Symbol.hasInstance, { value: function mixinHasInstance(other: any) { return has(other, func); } }); } } return mixinOnce; }