import { Mixin } from "../../interfaces/index"; import { Class } from "typescript-class-types"; export default class MixinsResolver { public constructor(public rewritesCollection: WeakMap, string[]>) { } public use< Rewrites extends {}, Requirements, T extends Rewrites & Requirements, >( constructor: Class, mixinClass: Class>, ) { const mixinCollection = new WeakMap>(); const mixinRewrites = class extends Object.getPrototypeOf(constructor) { constructor(...args: any[]) { super(...args); const mixin = new mixinClass(); mixin.owner = this as unknown as T; mixinCollection.set(this as unknown as T, mixin); } }; for(const thingNameToRewrite of this.getRewritesByMixinClass(mixinClass)) { Object.defineProperty(mixinRewrites.prototype, thingNameToRewrite, { get: function() { const mixin = mixinCollection.get(this); if(!mixin) { throw new Error("Failed to get mixin instance."); } if(typeof mixin[thingNameToRewrite] === "function") { return (...args: unknown[]) => { return mixin[thingNameToRewrite](...args); }; } else { return mixin[thingNameToRewrite]; } }, set: function(value: unknown) { const mixin = mixinCollection.get(this); if(!mixin) { throw new Error("Failed to get mixin instance."); } mixin[thingNameToRewrite] = value; }, }); } Object.setPrototypeOf(constructor, mixinRewrites); Object.setPrototypeOf(constructor.prototype, mixinRewrites.prototype); } public rewrite< Rewrites extends {}, T extends Mixin, >( target: T, property: keyof Rewrites & string, ): void { const rewritesCollection = this.getOrSet(target); rewritesCollection.push(property); } protected getRewritesByMixinClass< Rewrites extends {}, Requirements, >( mixinClass: Class>, ): string[] { const rewrites: string[] = []; let mixin = mixinClass; do { const mixinRewrites = this.rewritesCollection.get(mixin.prototype); if(Array.isArray(mixinRewrites)) { rewrites.push(...mixinRewrites); } mixin = Object.getPrototypeOf(mixin); } while(mixin); return rewrites; } protected getOrSet>(target: T): string[] { const issetRewritesCollection = this.rewritesCollection.get(target); if(issetRewritesCollection === undefined) { const rewritesCollection = []; this.rewritesCollection.set(target, rewritesCollection); return rewritesCollection; } return issetRewritesCollection; } }