UNPKG

foibles

Version:

Composition and mixins and TypeScript classes

244 lines (231 loc) 6.47 kB
import { MixinFunction, AnyConstructor } from './mixin'; /** * Type generator for base types. Used together with `toExtendable` to define * a base type that supports mixins: * * ```typescript * type ExampleBaseType = Extendable<typeof ExampleBaseType>; * const ExampleBaseType = toExtendable(class ExampleBaseType { * ... * }); * ``` */ export type Extendable<T extends AnyConstructor<any>> = InstanceType<T>; /** * Functions used to extend a class with mixins. Provides the `with` method * that can be invoked statically with up to 8 mixins. * * ```typescript * const NewType = BaseType.with(MixinHere); * * const OtherType = BaseType.with(MixinHere, OtherMixinHere); * ``` */ export interface ExtendableFunctions<T extends object> extends AnyConstructor<T> { /** * Extend the class with functionality from the given mixin. * * @param m1 */ with< M1 extends AnyConstructor<any> >( m1: MixinFunction<this, M1> ): this & M1 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. * * @param m1 * @param m2 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2> ): this & M1 & M2 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3> ): this & M1 & M2 & M3 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 * @param m4 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any>, M4 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3>, m4: MixinFunction<this, M4> ): this & M1 & M2 & M3 & M4 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 * @param m4 * @param m5 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any>, M4 extends AnyConstructor<any>, M5 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3>, m4: MixinFunction<this, M4>, m5: MixinFunction<this, M5> ): this & M1 & M2 & M3 & M4 & M5 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 * @param m4 * @param m5 * @param m6 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any>, M4 extends AnyConstructor<any>, M5 extends AnyConstructor<any>, M6 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3>, m4: MixinFunction<this, M4>, m5: MixinFunction<this, M5>, m6: MixinFunction<this, M6> ): this & M1 & M2 & M3 & M4 & M5 & M6 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 * @param m4 * @param m5 * @param m6 * @param m7 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any>, M4 extends AnyConstructor<any>, M5 extends AnyConstructor<any>, M6 extends AnyConstructor<any>, M7 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3>, m4: MixinFunction<this, M4>, m5: MixinFunction<this, M5>, m6: MixinFunction<this, M6>, m7: MixinFunction<this, M7> ): this & M1 & M2 & M3 & M4 & M5 & M6 & M7 & ExtendableFunctions<T>; /** * Extend the class with functionality from the given mixins. Mixins will * be applied left to right, so the first mixin extends the class, the * second mixin extends the result of first mixin and so on. * * @param m1 * @param m2 * @param m3 * @param m4 * @param m5 * @param m6 * @param m7 * @param m8 */ with< M1 extends AnyConstructor<any>, M2 extends AnyConstructor<any>, M3 extends AnyConstructor<any>, M4 extends AnyConstructor<any>, M5 extends AnyConstructor<any>, M6 extends AnyConstructor<any>, M7 extends AnyConstructor<any>, M8 extends AnyConstructor<any> >( m1: MixinFunction<this, M1>, m2: MixinFunction<this, M2>, m3: MixinFunction<this, M3>, m4: MixinFunction<this, M4>, m5: MixinFunction<this, M5>, m6: MixinFunction<this, M6>, m7: MixinFunction<this, M7>, m8: MixinFunction<this, M8> ): this & M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & ExtendableFunctions<T>; } /** * Take the given base class and turn it into an extendable class, giving it * a static method `with` that can be used to apply mixins. * * When using TypeScript this should be accompanied with a type definition * using `Extendable`: * * ```typescript * type ExampleBaseType = Extendable<typeof ExampleBaseType>; * const ExampleBaseType = toExtendable(class ExampleBaseType { * ... * }); * ``` * * @param o */ export function toExtendable<O extends AnyConstructor<any>>(o: O) { Object.defineProperty(o, 'with', { enumerable: false, value: mixer }); return o as O & ExtendableFunctions<O>; } const mixer = function(this: any, ...mixins: MixinFunction<AnyConstructor<any>, any>[]) { let result = this; for(const mixin of mixins) { result = mixin(result); } return result; };