-
Notifications
You must be signed in to change notification settings - Fork 2
/
deep-proxy-polyfill.ts
90 lines (75 loc) · 2.25 KB
/
deep-proxy-polyfill.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
export type GetHandler<T> = (obj: T, key: keyof T, root: Object, keys: Array<keyof T>) => any;
export type SetHandler<T> = (obj: T, key: keyof T, value: any, root: Object, keys: Array<keyof T>) => any;
export interface Handler<T> {
get?: GetHandler<T>;
set?: SetHandler<T>;
}
const isProxyable = (obj: any): obj is Object =>
typeof obj === 'object' &&
obj !== null &&
(
// { }
obj.constructor === Object ||
// Object.create(null)
Object.getPrototypeOf(obj) === null
);
const recursiveDeepProxy = <Shape>(
target: any,
handler: Handler<Shape>,
root: Object,
keys: Array<keyof Shape>,
) => {
// If this object can't be proxied, return it as-is.
if (!isProxyable(target)) {
return target;
}
const getHandler: GetHandler<Shape> | undefined = handler.get;
const setHandler: SetHandler<Shape> | undefined = handler.set;
return (Object.keys(target) as Array<keyof Shape>).reduce(
(accumulator: Object, key: keyof Shape): Object => {
const attributes: PropertyDescriptor & ThisType<any> = {
configurable: false,
enumerable: true
};
// Custom getter
if (getHandler) {
attributes.get = (): any => {
return recursiveDeepProxy(
getHandler(target, key, root, keys),
handler, root, keys.concat(key)
);
};
}
// Default getter
else {
attributes.get = (): any => {
return recursiveDeepProxy(
target[key],
handler, root, keys.concat(key)
);
};
}
// Custom setter
if (setHandler) {
attributes.set = (value: any): void => {
setHandler(target, key, value, root, keys);
};
}
// Default setter
else {
attributes.set = (value: any): void => {
target[key] = value;
};
}
Object.defineProperty(accumulator, key, attributes);
return accumulator;
},
// If the original Object has no prototype, neither should this one.
Object.getPrototypeOf(target) === null ?
Object.create(null) :
{}
);
};
export default function deepProxy<Shape>(target: Shape, handler: Handler<Shape> = {}): Shape {
return recursiveDeepProxy(target, handler, target, []);
};