Skip to content

Instantly share code, notes, and snippets.

@bigmistqke
Last active January 2, 2025 02:16
Show Gist options
  • Save bigmistqke/b783ae9b4b76f36a52fce2abc9ba4611 to your computer and use it in GitHub Desktop.
Save bigmistqke/b783ae9b4b76f36a52fce2abc9ba4611 to your computer and use it in GitHub Desktop.
createPatchProxy
type Patch = {
op: "add" | "update" | "delete";
path: string[];
value?: any;
};
const $PATCH = Symbol();
function createPatchProxy<T extends object>(
target: T,
basePath: string[] = [],
) {
const patches = new Array<Patch>();
function createProxy<T extends object>(target: T, currentPath: string[]): T {
return new Proxy<T>(target, {
get(obj, prop) {
if (prop === $PATCH) {
return true;
}
const value = obj[prop];
if (typeof value === "object" && value !== null) {
if (!obj[$PATCH]) {
obj[prop] = Array.isArray(value) ? [...value] : { ...value };
}
return createProxy(obj[prop], [...currentPath, prop.toString()]);
}
return value;
},
set(obj, prop, value) {
const fullPath = [...currentPath, prop.toString()];
if (prop in obj) {
patches.push({ op: "update", path: fullPath, value });
} else {
patches.push({ op: "add", path: fullPath, value });
}
target[prop] = value;
return true;
},
deleteProperty(obj, prop) {
if (prop in obj) {
const fullPath = [...currentPath, prop.toString()];
patches.push({ op: "delete", path: fullPath });
delete obj[prop];
return true;
}
return false;
},
});
}
function apply(target: T): void {
for (const patch of patches) {
const { op, path, value } = patch;
// Traverse the object to the second-to-last key
const lastKey = path[path.length - 1];
const parent = path.slice(0, -1).reduce((acc, key) => {
if (!(key in acc)) {
throw new Error(`Path not found: ${path.join(".")}`);
}
return acc[key];
}, target);
switch (op) {
case "add":
case "update":
parent[lastKey] = value;
break;
case "delete":
if (lastKey in parent) {
delete parent[lastKey];
} else {
throw new Error(`Key not found for deletion: ${path.join(".")}`);
}
break;
default:
throw new Error(`Unknown operation: ${op}`);
}
}
}
return {
clear: () => (patches.length = 0),
proxy: createProxy<T>({ ...target }, basePath),
patches,
apply,
};
}
// Usage
const [store, setStore] = createStore({ id: "hallo", address: ["ok", "sure"] });
const { proxy, patches, apply, clear } = createPatchProxy(store);
proxy.address[1] = "Bob";
proxy.address[2] = "Bob";
proxy.test = "hallo";
console.log(patches)
console.log(store, proxy);
setStore(produce(apply));
console.log(store, proxy);
clear()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment