Skip to content

Instantly share code, notes, and snippets.

@buttercrab
Created July 12, 2024 03:28
Show Gist options
  • Save buttercrab/2d354c52aa9f4d7f1d1f42f7bf45e7da to your computer and use it in GitHub Desktop.
Save buttercrab/2d354c52aa9f4d7f1d1f42f7bf45e7da to your computer and use it in GitHub Desktop.
// Example usage
const data = { a: 1, b: { c: 2, d: [1, 2, 3] } };
const data2 = JSON.parse(JSON.stringify(data));
const proxy = createProxy(data, (diff) => {
applyDiff(data2, diff);
console.log(data2, diff);
});
proxy.a = 2;
proxy.b.c = 3;
proxy.b.d.push(4);
proxy.b.d.splice(0, 1);
proxy.b.d = [1, 2];
proxy.e = {a: 1};
delete proxy.a;
delete proxy.b.c;
console.log(data, data2);
export const enum DiffType {
ADD,
UPDATE,
REMOVE
}
export type DiffPartial<T> = {
[P in keyof T]?:
T[P] extends (infer U)[] ? DiffPartial<U>[] | ({ [index: number]: DiffPartial<U> }) :
T[P] extends object | undefined ? DiffPartial<T[P]> :
T[P];
};
export type Diff<T> = {
type: DiffType,
value: DiffPartial<T>,
};
export function createProxy<T extends object>(data: T, callback: (diff: Diff<T>) => void): T {
return new Proxy(data, {
set(target: T, property, value) {
const prevValue = target[property as keyof T];
if (prevValue !== value) {
const diffType = (property in target) ? DiffType.UPDATE : DiffType.ADD;
target[property as keyof T] = value;
callback({type: diffType, value: {[property as keyof T]: value} as DiffPartial<T>});
}
return true;
},
deleteProperty(target: T, property) {
if (property in target) {
delete target[property as keyof T];
callback({type: DiffType.REMOVE, value: {[property as keyof T]: undefined}} as Diff<T>);
}
return true;
},
get(target: T, property, receiver) {
const prop = property as keyof T;
if (property === '__isProxy__') return true;
const value = target[prop] as T[typeof prop] & { __isProxy__: boolean };
if (typeof value === 'object' && value !== null && !value.__isProxy__) {
return createProxy(value, (p) => {
callback({type: p.type, value: {[prop]: p.value} as DiffPartial<T>} as Diff<T>);
});
}
return value;
}
});
}
export function applyDiff<T>(target: T, diff: Diff<T>): void {
switch (diff.type) {
case DiffType.ADD:
applyAdd(target, diff.value);
break;
case DiffType.UPDATE:
applyPartial(target, diff.value);
break;
case DiffType.REMOVE:
applyRemove(target, diff.value);
break;
}
}
function applyAdd<T>(target: T, partial: DiffPartial<T>): void {
for (const key in partial) {
const value = partial[key];
if (target.hasOwnProperty(key)) {
applyAdd(target[key], value as DiffPartial<T[keyof T]>);
} else if (Array.isArray(target)) {
(target as any[]).push(value);
} else {
target[key] = value as T[Extract<keyof T, string>];
}
}
}
function applyPartial<T>(target: T, partial: DiffPartial<T>): void {
for (const key in partial) {
const value = partial[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
applyPartial(target[key], value);
} else {
target[key] = value as T[Extract<keyof T, string>];
}
}
}
function applyRemove<T>(target: T, partial: DiffPartial<T>): void {
for (const key in partial) {
const value = partial[key];
if (value !== undefined) {
applyRemove(target[key], value as DiffPartial<T[keyof T]>);
} else if (target.hasOwnProperty(key)) {
delete target[key];
} else if (Array.isArray(target)) {
const index = parseInt(key, 10);
(target as any[]).splice(index, 1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment