forked from sindresorhus/on-change
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
156 lines (117 loc) · 3.44 KB
/
index.js
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'use strict';
const isPrimitive = value => value === null || (typeof value !== 'object' && typeof value !== 'function');
const concatPath = (path, property) => {
if (property && property.toString) {
if (path) {
path += '.';
}
path += property.toString();
}
return path;
};
const proxyTarget = Symbol('ProxyTarget');
const onChange = (object, onChange, options = {}) => {
let inApply = false;
let changed = false;
const propCache = new WeakMap();
const pathCache = new WeakMap();
const proxyCache = new WeakMap();
const handleChange = (path, property, previous, value) => {
if (!inApply) {
onChange.call(proxy, concatPath(path, property), value, previous);
} else if (!changed) {
changed = true;
}
};
const getOwnPropertyDescriptor = (target, property) => {
let props = propCache.get(target);
if (props) {
return props;
}
props = new Map();
propCache.set(target, props);
let prop = props.get(property);
if (!prop) {
prop = Reflect.getOwnPropertyDescriptor(target, property);
props.set(property, prop);
}
return prop;
};
const invalidateCachedDescriptor = (target, property) => {
const props = propCache.get(target);
if (props) {
props.delete(property);
}
};
const handler = {
get(target, property, receiver) {
if (property === proxyTarget) {
return target;
}
const value = Reflect.get(target, property, receiver);
if (isPrimitive(value) || property === 'constructor' || options.isShallow === true) {
return value;
}
// Preserve invariants
const descriptor = getOwnPropertyDescriptor(target, property);
if (descriptor && !descriptor.configurable) {
if (descriptor.set && !descriptor.get) {
return undefined;
}
if (descriptor.writable === false) {
return value;
}
}
pathCache.set(value, concatPath(pathCache.get(target), property));
let proxy = proxyCache.get(value);
if (proxy === undefined) {
proxy = new Proxy(value, handler);
proxyCache.set(value, proxy);
}
return proxy;
},
set(target, property, value, receiver) {
if (value && value[proxyTarget] !== undefined) {
value = value[proxyTarget];
}
const previous = Reflect.get(target, property, receiver);
const result = Reflect.set(target, property, value);
if (previous !== value) {
handleChange(pathCache.get(target), property, previous, value);
}
return result;
},
defineProperty(target, property, descriptor) {
const result = Reflect.defineProperty(target, property, descriptor);
invalidateCachedDescriptor(target, property);
handleChange(pathCache.get(target), property, undefined, descriptor.value);
return result;
},
deleteProperty(target, property) {
const previous = Reflect.get(target, property);
const result = Reflect.deleteProperty(target, property);
invalidateCachedDescriptor(target, property);
handleChange(pathCache.get(target), property, previous);
return result;
},
apply(target, thisArg, argumentsList) {
if (!inApply) {
inApply = true;
const result = Reflect.apply(target, thisArg, argumentsList);
if (changed) {
onChange();
}
inApply = false;
changed = false;
return result;
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
pathCache.set(object, '');
const proxy = new Proxy(object, handler);
return proxy;
};
module.exports = onChange;
// TODO: Remove this for the next major release
module.exports.default = onChange;