2023-03-16 07:14:34 -07:00
|
|
|
import type { IDataObject } from './Interfaces';
|
|
|
|
|
2023-04-03 08:19:12 -07:00
|
|
|
const defaultPropertyDescriptor = Object.freeze({ enumerable: true, configurable: true });
|
|
|
|
|
2023-03-30 04:29:36 -07:00
|
|
|
const augmentedObjects = new WeakSet<object>();
|
|
|
|
|
2023-04-03 08:18:52 -07:00
|
|
|
function augment<T>(value: T): T {
|
|
|
|
if (typeof value !== 'object' || value === null || value instanceof RegExp) return value;
|
2023-03-29 12:36:56 -07:00
|
|
|
if (value instanceof Date) return new Date(value.valueOf()) as T;
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
|
if (Array.isArray(value)) return augmentArray(value) as T;
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
|
return augmentObject(value) as T;
|
|
|
|
}
|
|
|
|
|
2023-03-16 07:14:34 -07:00
|
|
|
export function augmentArray<T>(data: T[]): T[] {
|
2023-04-03 08:18:52 -07:00
|
|
|
if (augmentedObjects.has(data)) return data;
|
|
|
|
|
2023-03-16 07:14:34 -07:00
|
|
|
let newData: unknown[] | undefined = undefined;
|
|
|
|
|
|
|
|
function getData(): unknown[] {
|
|
|
|
if (newData === undefined) {
|
|
|
|
newData = [...data];
|
|
|
|
}
|
|
|
|
return newData;
|
|
|
|
}
|
|
|
|
|
2023-04-03 08:18:52 -07:00
|
|
|
const proxy = new Proxy(data, {
|
2023-03-16 07:14:34 -07:00
|
|
|
deleteProperty(target, key: string) {
|
|
|
|
return Reflect.deleteProperty(getData(), key);
|
|
|
|
},
|
|
|
|
get(target, key: string, receiver): unknown {
|
|
|
|
const value = Reflect.get(newData !== undefined ? newData : target, key, receiver) as unknown;
|
2023-03-29 12:36:56 -07:00
|
|
|
const newValue = augment(value);
|
|
|
|
if (newValue !== value) {
|
2023-03-16 07:14:34 -07:00
|
|
|
newData = getData();
|
2023-03-29 12:36:56 -07:00
|
|
|
Reflect.set(newData, key, newValue);
|
|
|
|
return newValue;
|
2023-03-16 07:14:34 -07:00
|
|
|
}
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
getOwnPropertyDescriptor(target, key) {
|
|
|
|
if (newData === undefined) {
|
|
|
|
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (key === 'length') {
|
|
|
|
return Reflect.getOwnPropertyDescriptor(newData, key);
|
|
|
|
}
|
2023-04-03 08:19:12 -07:00
|
|
|
|
|
|
|
return Object.getOwnPropertyDescriptor(data, key) ?? defaultPropertyDescriptor;
|
2023-03-16 07:14:34 -07:00
|
|
|
},
|
|
|
|
has(target, key) {
|
|
|
|
return Reflect.has(newData !== undefined ? newData : target, key);
|
|
|
|
},
|
|
|
|
ownKeys(target) {
|
|
|
|
return Reflect.ownKeys(newData !== undefined ? newData : target);
|
|
|
|
},
|
|
|
|
set(target, key: string, newValue: unknown) {
|
2023-04-03 08:18:52 -07:00
|
|
|
// Always proxy all objects. Like that we can check in get simply if it
|
|
|
|
// is a proxy and it does then not matter if it was already there from the
|
|
|
|
// beginning and it got proxied at some point or set later and so theoretically
|
|
|
|
// does not have to get proxied
|
|
|
|
return Reflect.set(getData(), key, augment(newValue));
|
2023-03-16 07:14:34 -07:00
|
|
|
},
|
|
|
|
});
|
2023-04-03 08:18:52 -07:00
|
|
|
|
|
|
|
augmentedObjects.add(proxy);
|
|
|
|
return proxy;
|
2023-03-16 07:14:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export function augmentObject<T extends object>(data: T): T {
|
2023-04-03 08:18:52 -07:00
|
|
|
if (augmentedObjects.has(data)) return data;
|
|
|
|
|
2023-03-16 07:14:34 -07:00
|
|
|
const newData = {} as IDataObject;
|
|
|
|
const deletedProperties: Array<string | symbol> = [];
|
|
|
|
|
2023-04-03 08:18:52 -07:00
|
|
|
const proxy = new Proxy(data, {
|
2023-03-16 07:14:34 -07:00
|
|
|
get(target, key: string, receiver): unknown {
|
|
|
|
if (deletedProperties.indexOf(key) !== -1) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newData[key] !== undefined) {
|
|
|
|
return newData[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
|
|
const value = Reflect.get(target, key, receiver);
|
2023-03-29 12:36:56 -07:00
|
|
|
const newValue = augment(value);
|
|
|
|
if (newValue !== value) {
|
|
|
|
Object.assign(newData, { [key]: newValue });
|
|
|
|
return newValue;
|
2023-03-16 07:14:34 -07:00
|
|
|
}
|
|
|
|
|
2023-03-29 12:36:56 -07:00
|
|
|
return value;
|
2023-03-16 07:14:34 -07:00
|
|
|
},
|
|
|
|
deleteProperty(target, key: string) {
|
|
|
|
if (key in newData) {
|
|
|
|
delete newData[key];
|
|
|
|
}
|
|
|
|
if (key in target) {
|
|
|
|
deletedProperties.push(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
set(target, key: string, newValue: unknown) {
|
|
|
|
if (newValue === undefined) {
|
|
|
|
if (key in newData) {
|
|
|
|
delete newData[key];
|
|
|
|
}
|
|
|
|
if (key in target) {
|
|
|
|
deletedProperties.push(key);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
newData[key] = newValue as IDataObject;
|
|
|
|
|
|
|
|
const deleteIndex = deletedProperties.indexOf(key);
|
|
|
|
if (deleteIndex !== -1) {
|
|
|
|
deletedProperties.splice(deleteIndex, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
2023-04-03 08:19:12 -07:00
|
|
|
|
2023-03-16 07:14:34 -07:00
|
|
|
ownKeys(target) {
|
2023-04-03 08:19:12 -07:00
|
|
|
const originalKeys = Reflect.ownKeys(target);
|
|
|
|
const newKeys = Object.keys(newData);
|
|
|
|
return [...new Set([...originalKeys, ...newKeys])].filter(
|
2023-03-16 07:14:34 -07:00
|
|
|
(key) => deletedProperties.indexOf(key) === -1,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2023-04-03 08:19:12 -07:00
|
|
|
getOwnPropertyDescriptor(target, key) {
|
|
|
|
return Object.getOwnPropertyDescriptor(data, key) ?? defaultPropertyDescriptor;
|
2023-03-16 07:14:34 -07:00
|
|
|
},
|
|
|
|
});
|
2023-04-03 08:18:52 -07:00
|
|
|
|
|
|
|
augmentedObjects.add(proxy);
|
|
|
|
return proxy;
|
2023-03-16 07:14:34 -07:00
|
|
|
}
|