type BeforeFunc = (args: any[]) => void; type InsteadFunc = (args: any[], orig: (...args: any) => any) => any; type AfterFunc = (args: any[], res: any) => any; function wackyPatch( parentObj: any, name: string, patches: { before?: BeforeFunc; instead?: InsteadFunc; after?: AfterFunc; } ) { if (typeof parentObj === "string") { //assume parentObj and name are switched around (for backcompat/convienence) const tmp = parentObj; parentObj = name; name = tmp; } const injId = Symbol(); const before = patches["before"]; const instead = patches["instead"]; const after = patches["after"]; const handler = { apply: ( target: () => any, thisArg: any, [ctx, args]: [ctx: any, args: []] ) => { if (before !== undefined) before.apply(ctx, [args]); const res = patches["instead"] !== undefined ? //@ts-ignore; TS thinks that `instead` possibly being undefined is bad (meanwhile we literally check for that) instead.apply(ctx, [args, target.bind(ctx)]) : target.apply(ctx, args); if (after === undefined) return res; return after.apply(ctx, [args, res]); } }; const prox = new Proxy(parentObj[name], handler); const orig = parentObj[name]; parentObj[name] = function () { return prox(this, arguments); }; const unpatch = () => { parentObj[name] = orig; }; parentObj[injId] = { name: name, orig: orig, unpatch: unpatch }; return unpatch; } function before(parentObj: any, name: string, func: BeforeFunc) { return wackyPatch(parentObj, name, { before: func }); } function instead(parentObj: any, name: string, func: InsteadFunc) { return wackyPatch(parentObj, name, { instead: func }); } function after(parentObj: any, name: string, func: AfterFunc) { return wackyPatch(parentObj, name, { after: func }); } export { instead, before, after }; export default { instead, before, after };