[Typescript] Builder pattern - 06 Exercise
type TMethodListener<T> = (copyFn: T, ...args: any[]) => void;
type TBuildInfo<TOverriden> = {
mutationList: TOverriden;
error?: string;
};
/**
* @description
*
* Overriden class helps to overriden object.
*
* Prevent override the methods which already been overriden;
* This is not applied to props
*
* .method(methodName, callback: (originalFunction: Function, ...args: any[]) => void)
* .prop(propName, callback: (prevVal) => newVal): overriden the prop with new value]
* .build(callback?: (overridenList) => void): get final overriden object back,
* callback function get all the overriden props and methods array
*/
class Overriden<TMap extends object = {}, TOverriden extends string[] = []> {
#map: TMap;
#overriden = [] as any as TOverriden;
#done = false;
#error?: string;
constructor(obj: TMap) {
this.#map = obj;
}
build(callback?: (info: TBuildInfo<TOverriden>) => void) {
this.#checkDone();
this.#done = true;
if (callback && typeof callback === "function") {
callback({
mutationList: this.#overriden,
error: this.#error,
});
}
return this.#map;
}
prop<TPropName extends keyof TMap & string, RT>(
propName: TPropName extends TOverriden[number] ? never : TPropName,
listener: (prevVal: TMap[TPropName]) => RT
): Overriden<Record<TPropName, RT> & TMap, [...TOverriden, TPropName]> {
this.#checkDone();
(this.#map as any)[propName] = listener(this.#map[propName]);
this.#overriden.push(propName);
return this as any;
}
method<TMethodName extends keyof TMap & string>(
methodName: TMethodName extends TOverriden[number] ? never : TMethodName,
listener: TMethodListener<TMap[TMethodName]>
): TMethodName extends TOverriden[number]
? Overriden<TMap, TOverriden>
: Overriden<
Record<TMethodName, TMethodListener<TMap[TMethodName]>> & TMap,
[...TOverriden, TMethodName]
> {
this.#checkDone();
if (typeof this.#map[methodName] !== "function") {
this.#error = `${methodName} not a function`;
throw new Error(this.#error);
}
if (this.#overriden.includes(methodName)) {
this.#error = `${methodName} got overriden already`;
console.warn(this.#error);
return this as any;
}
const copyFn = this.#map[methodName];
(this.#map as any)[methodName] = (...args: any[]) => {
listener.call(this.#map, copyFn, ...args);
};
this.#overriden.push(methodName);
return this as any;
}
#checkDone(): asserts this is this & { error: undefined } {
if (this.#done) {
this.#error =
"There is already another overriden process for the same object";
throw new Error(this.#error);
}
}
}
const win = {
close() {
console.log("close is called");
},
open(...args: any[]) {
console.log("open is called", ...args);
return args[0];
},
alert() {
console.log("alert is called");
},
isObj: true,
type: "window",
info: {
name: "name",
age: 12,
},
};
const overridenWin = new Overriden(win);
const chain = overridenWin
.prop("info", (prevInfo) => {
return { ...prevInfo, address: "new address" };
})
.method("open", (copyFn, ...args) => {
copyFn(...args);
});
// do something here
//
//
const res = chain.method("close", () => {}).build(console.log);
res.info.address;
res.open();