Last active
July 6, 2025 23:52
-
-
Save mini-ninja-64/a37961bf1acb8b9d00f3a4a0be069417 to your computer and use it in GitHub Desktop.
JavaScript/TypeScript Proxy object helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const targetObject = { | |
message1: "hello", | |
message2: "everyone", | |
test1: function () { console.log(this); }, | |
test2: () => { console.log(this); }, | |
test3: () => ({ nest1: () => ({ nest2: () => "value" }) }), | |
test4: { foo: { bar: () => ({ a: 1 }) } }, | |
}; | |
const proxy1 = proxify(targetObject, { | |
functionExecCallback: (callChain) => { | |
if (callChain.matches(".test4.foo.bar.()")) { | |
console.log("hhehhehehe ive highjacked uuuu"); | |
} | |
} | |
}); | |
proxy1.test1() | |
console.log(proxy1.message1) | |
proxy1.test2() | |
console.log(proxy1.message2) | |
console.log(proxy1.test3().nest1().nest2()) | |
console.log(proxy1.test4.foo.bar()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Note: Will probs move to being a library at some point | |
// Note: Currently a WIP | |
export type BaseTypes = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"; | |
export interface JsProxyProperties { value: any; receiver: any; target: any; } | |
export interface ProxifyInternal { | |
rawValue: any; | |
valueCallbackResult: any; | |
} | |
const PROXIFY_INTERNAL_KEY = "__proxify_internal" | |
function proxifyValue({ value, receiver, target }: JsProxyProperties, currentCaller: CallChain, options: ProxifyOptions): any { | |
const valueCallbackReturn = options.valueCallback(currentCaller, value); | |
const normalisedValue = valueCallbackReturn === undefined ? value : valueCallbackReturn.value; | |
const internalFields = { [PROXIFY_INTERNAL_KEY]: { rawValue: value, valueCallbackResult: valueCallbackReturn } }; | |
if (normalisedValue instanceof Promise) { | |
let promise = new Promise((resolve, reject) => { | |
normalisedValue.then((resolvedValue) => { | |
resolve(proxifyValue({ value: resolvedValue, receiver, target }, currentCaller.extend("resolved"), options)); | |
}).catch(rejectedValue => { | |
reject(proxifyValue({ value: rejectedValue, receiver, target }, currentCaller.extend("rejected"), options)); | |
}) | |
}); | |
return Object.assign(promise, internalFields); | |
} else if (normalisedValue instanceof Function) { | |
// Do not make this an arrow function, it wont work for *this* reasons :p | |
const f = function (...actualArgs: any[]) { | |
const t = this === receiver ? target : this; | |
const actualFunction = (...args: any[]) => normalisedValue.apply(t, args); | |
currentCaller = currentCaller.extend("executed"); | |
const functionExeccallbackReturn = options.functionExecCallback(currentCaller, actualArgs, actualFunction); | |
const result = functionExeccallbackReturn === undefined ? actualFunction(...actualArgs) : functionExeccallbackReturn.value; | |
return proxifyValue({ value: result, receiver, target }, currentCaller, options); | |
}; | |
return Object.assign(f, internalFields); | |
} else if (normalisedValue instanceof Object) { | |
var p = new Proxy(normalisedValue, handler(currentCaller, options)); | |
return Object.assign(p, internalFields); | |
} | |
return normalisedValue; | |
} | |
function handler(caller: CallChain = new CallChain([]), options: ProxifyOptions) { | |
return { | |
get(target: any, property: string, receiver: any): any { | |
let value = target[property]; | |
if (property === PROXIFY_INTERNAL_KEY) return value; | |
let currentCaller = caller.extend({ name: property, type: typeof value }); | |
return proxifyValue({ value, receiver, target }, currentCaller, options); | |
} | |
} | |
} | |
interface BaseCaller { | |
name: string | |
type: BaseTypes | |
} | |
export type Caller = BaseCaller | "executed" | "resolved" | "rejected" | |
export class CallChain { | |
chain: Caller[] | |
constructor(chain: Caller[]) { | |
this.chain = chain; | |
} | |
extend(caller: Caller): CallChain { | |
return new CallChain([...this.chain, caller]); | |
} | |
toCallChainString(): string { | |
return this.chain | |
.map(call => `.${callerString(call)}`) | |
.join(''); | |
} | |
toString(): string { | |
return this.chain.toString(); | |
} | |
} | |
function callerString(caller: Caller) { | |
switch (caller) { | |
case "executed": return "()"; | |
case "resolved": return "__asyncResolved()"; | |
case "rejected": return "__asyncRejected()"; | |
default: return caller.name; | |
} | |
} | |
export type ProxifyReturn = { value: any } | void | |
export interface ProxifyOptions { | |
valueCallback: (caller: CallChain, rawValue: any) => ProxifyReturn, | |
functionExecCallback: (caller: CallChain, args: any[], func: (...args: any[]) => any) => ProxifyReturn | |
} | |
export function proxify<T extends object>(target: T, proxyifyOptions: Partial<ProxifyOptions> = {}): T { | |
const defaults: ProxifyOptions = { | |
valueCallback: () => { }, | |
functionExecCallback: () => { } | |
} | |
const options = { ...defaults, ...proxyifyOptions } | |
return new Proxy(target, handler(new CallChain([]), options)) | |
} | |
export function unproxy<T extends object>(proxied: T): T { | |
if (PROXIFY_INTERNAL_KEY in proxied) { | |
var internals = proxied[PROXIFY_INTERNAL_KEY] as ProxifyInternal; | |
return internals.rawValue | |
} | |
return proxied; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment