Track why, when, and where your JavaScript objects changed — like Git blame for plain objects.
Most JavaScript developers know Proxy exists. Almost none have built something real with it.
I was in that group. I’d seen it mentioned in Vue 3’s source, skimmed the MDN page, moved on. It felt like advanced plumbing — impressive, but not something I needed to touch.
Then I hit a bug that changed that. A shared object was mutating across three files. I had no idea which one ran last, or in what order the values flipped. I added console.log in four places. I still couldn’t tell. The value was just… wrong, and the trail was cold.
That frustration turned into a question: what if the object itself remembered?
Proxy lets you wrap any plain object and intercept every operation on it — reads, writes, deletes. When you assign user.name = "John", a set trap fires before the value lands. You can observe it, record it, block it entirely.
const user = new Proxy({ name: "Hritik" }, {
set(target, key, value) {
console.log(`${key} is being set to ${value}`);
return Reflect.set(target, key, value);
}
});
user.name = "John"; // logs: name is being set to John
That Reflect.set is important — it performs the actual write correctly, respecting prototype chains and getters. Early tutorials skip it. You end up with subtle bugs in edge cases. The right mental model: Proxy is the checkpoint, Reflect is the stamp that lets the operation through unchanged.
The second piece was stranger. Inside the set trap, you can capture a stack trace — new Error().stack — at the exact moment of mutation. Parse it, skip the library’s own frames, and the first frame that survives is whoever wrote obj.prop = value in your app. File name, line number, function name. No bundler plugin. No build step. Just a string you parse at runtime.
The third piece was WeakMap. History has to live somewhere, but a regular Map holds a strong reference — your object can never be garbage collected as long as the map exists. WeakMap keys are held weakly. When nothing else references your object, the entry disappears on its own. The debug data doesn’t outlive the thing it was watching.
Those three things — Proxy, Error.stack, WeakMap — clicked together into something useful.
const user = track({ name: "Hritik" });
user.name = "John"; // ProfileForm.tsx:24
user.name = "Jane"; // UserSettings.tsx:87
why(user, "name");
// Changed 2 times
// 1. Hritik → John ProfileForm.tsx:24 10:22 PM
// 2. John → Jane UserSettings.tsx:87 10:45 PM
I called it WhyNotJS. One function to wrap an object, one to ask what happened to it.
What surprised me most wasn’t the Proxy mechanics. It was realising this exact pattern already runs in production everywhere. Vue 3’s reactive() is a Proxy that schedules re-renders on set.
That’s the thing about digging into low-level JavaScript. The language isn’t as mysterious as it looks from the outside. The frameworks aren’t magic. They’re just people who understood the primitives well enough to build something useful on top of them.