Last active
January 2, 2025 02:16
-
-
Save bigmistqke/b783ae9b4b76f36a52fce2abc9ba4611 to your computer and use it in GitHub Desktop.
createPatchProxy
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
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