-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
121 lines (105 loc) · 3.4 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
//export type PathPartType = 'function' | 'object' | 'string' | 'number';
export enum PathPartType {
function = 'function',
object = 'object',
string = 'string',
number = 'number',
boolean = 'boolean',
symbol = 'symbol',
undefined = 'undefined',
bigint = 'bigint',
}
export interface BasePathPart {
key: string;
type: Exclude<PathPartType, PathPartType.function>;
}
export interface FunctionPathPart {
key: string;
type: PathPartType.function;
callArgs: any[];
}
export type PathPart = BasePathPart | FunctionPathPart;
export interface TracePropAccessOptions {
callback?(paths: PathPart[][], result: any): void;
shouldFollow?(target: any, propKey: any): boolean;
}
const isDataObject = (obj: any) =>
Buffer.isBuffer(obj) || typeof obj.byteLength === 'number';
export function tracePropAccess(
obj: any,
options: TracePropAccessOptions,
paths: PathPart[][] = [[]],
): any {
const actualOptions = {
callback: () => {},
shouldFollow: () => true,
...options,
};
return new Proxy(obj, {
get(target: any, propKey: any) {
const reflectedProp = Reflect.get(target, propKey);
if (
!(propKey in target) ||
!actualOptions.shouldFollow(target, propKey)
) {
return reflectedProp;
}
const workingPaths = paths.map(pathArr => [...pathArr]);
const newPathEntry =
typeof reflectedProp === 'function'
? ({
key: propKey.toString(),
type: PathPartType.function,
callArgs: [],
} as FunctionPathPart)
: ({
key: propKey.toString(),
type: PathPartType[typeof reflectedProp],
} as BasePathPart);
workingPaths[workingPaths.length - 1] = [
...workingPaths[workingPaths.length - 1],
newPathEntry,
];
if (reflectedProp) {
if (typeof reflectedProp === 'object' && !isDataObject(reflectedProp)) {
return tracePropAccess(reflectedProp, actualOptions, workingPaths);
}
if (typeof reflectedProp === 'function') {
return function(...args: any) {
workingPaths[workingPaths.length - 1].pop();
workingPaths[workingPaths.length - 1].push({
key: propKey.toString(),
type: PathPartType.function,
callArgs: args,
});
const newPaths = [...workingPaths, []];
//@ts-ignore
const fnResult = reflectedProp.apply(target, args);
if (typeof fnResult !== 'object' || !fnResult) {
return fnResult;
}
if (typeof fnResult.then === 'function') {
return fnResult.then((result: any) => {
if (
typeof result === 'object' &&
result &&
!isDataObject(result)
) {
console.log('trapping!', isDataObject(result), result);
return tracePropAccess(result, actualOptions, newPaths);
}
newPaths.pop();
console.log('blat', newPaths, result);
actualOptions.callback(newPaths, result);
return result;
});
}
return tracePropAccess(fnResult, actualOptions, newPaths);
};
}
}
actualOptions.callback(workingPaths, target[propKey]);
return reflectedProp;
},
});
}