forked from tylerlong/manate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
133 lines (120 loc) · 3.77 KB
/
index.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
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
import {EventEmitter} from 'events';
import {ProxyEvent, Children} from './models';
const childrenKey = '&*()_+=-~`!@#$%^';
export type ProxyType<T> = T & {__emitter__: EventEmitter};
export const canProxy = (obj: object) =>
typeof obj === 'object' && obj !== null;
// release all children
export const releaseChildren = (obj: object): void => {
if (canProxy(obj)) {
(Reflect.get(obj, childrenKey) as Children)?.releasesAll();
}
};
export function useProxy<T extends object>(target: T): ProxyType<T> {
// return if the object is already a proxy
if ((target as ProxyType<T>).__emitter__) {
return target as ProxyType<T>;
}
// two variables belongs to the scope of useProxy (the proxy)
const emitter = new EventEmitter();
const children = new Children();
// make child a proxy and add it to children
const proxyChild = (path: string, value: any) => {
if (!canProxy(value)) {
return value;
}
const childProxy = useProxy(value);
children.addChild(path, childProxy.__emitter__, emitter);
return childProxy;
};
const proxy = new Proxy(target, {
get: (target: T, path: string, receiver?: T) => {
if (path === '__emitter__') {
return emitter;
}
if (path === childrenKey) {
return children;
}
const value = Reflect.get(target, path, receiver);
if (typeof value !== 'function' && typeof path !== 'symbol') {
emitter.emit('event', new ProxyEvent('get', [path]));
}
return value;
},
set: (target: T, path: string, value: any, receiver?: T): boolean => {
// no assign object to itself, doesn't make sense
// array.length assign oldValue === value, strange
if (canProxy(value) && value === Reflect.get(target, path)) {
return true;
}
// remove old child in case there is one
children.releaseChild(path);
Reflect.set(target, path, proxyChild(path, value), receiver);
if (typeof path !== 'symbol') {
emitter.emit('event', new ProxyEvent('set', [path]));
}
return true;
},
});
// first time init
for (const path of Object.keys(target)) {
const value = Reflect.get(target, path);
Reflect.set(target, path, proxyChild(path, value), target);
}
return proxy as ProxyType<T>;
}
export function run<T>(
proxy: ProxyType<T>,
func: Function
): [result: any, isTrigger: (event: ProxyEvent) => boolean] {
const events: ProxyEvent[] = [];
const listener = (event: ProxyEvent) => events.push(event);
proxy.__emitter__.on('event', listener);
const result = func();
proxy.__emitter__.off('event', listener);
const getPaths = [
...new Set(
events
.filter(event => event.name === 'get')
.map(event => event.pathString)
),
];
const isTrigger = (event: ProxyEvent): boolean => {
if (event.name === 'set') {
const setPath = event.pathString;
if (getPaths.some(getPath => getPath.startsWith(setPath))) {
// if setPath is shorter than getPath, then it's time to refresh
return true;
}
}
return false;
};
return [result, isTrigger];
}
export function autoRun<T>(
proxy: ProxyType<T>,
func: () => void,
decorator?: (func: () => void) => () => void
): {start: () => void; stop: () => void} {
let isTrigger: (event: ProxyEvent) => boolean = () => true;
const listener = (event: ProxyEvent) => {
if (isTrigger(event)) {
proxy.__emitter__.off('event', listener);
runOnce();
proxy.__emitter__.on('event', listener);
}
};
let runOnce = () => {
[, isTrigger] = run(proxy, func);
};
if (decorator) {
runOnce = decorator(runOnce);
}
return {
start: () => {
runOnce();
proxy.__emitter__.on('event', listener);
},
stop: () => proxy.__emitter__.off('event', listener),
};
}