-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
203 lines (180 loc) · 5.32 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import lodashGet from "lodash.get";
import lodashSet from "lodash.set";
/**
* Check if path has dot notation
* @param path
*/
function hasDotNotation(path: string) {
return path.indexOf(".") !== -1;
}
/**
* Only use lodash get if path has a dot notation
* @param obj
* @param path
*/
function liteGet<T = any>(obj: any, path: string): T | undefined {
if (hasDotNotation(path)) {
return lodashGet(obj, path) as T;
} else {
return obj[path] as T;
}
}
/**
* Only use lodash set if path has a dot notation
* @param obj
* @param path
* @param value
*/
function liteSet<T>(obj: any, path: string, value: T) {
if (hasDotNotation(path)) {
lodashSet(obj, path, value);
} else {
obj[path] = value;
}
}
/**
* Populate array Options Type
*/
export interface PopulateOptions<PathType = any, AllType = any> {
// as - rename the populated path
as?: string;
/**
* unique - if true, will only populate unique values
* When running `each` function, it will cache using a uniqueKey
* Thereby not running `each` function for duplicate values
*/
unique?: boolean;
/**
* use - if provided, will use this function will be called and its result will
* be passed to `each` function as second argument
* @param all
*/
use?: (all: PathType[]) => Promise<AllType> | AllType;
/**
* each - function to run for each item in the array.
* return value will be set to the populated path
* @param each
* @param allData
*/
each: (each: PathType, allData: AllType) => Promise<any> | any;
}
/**
* Populate array
* @param array
* @param path
* @param options
*/
export function populateArray<PathType = any, AllType = any>(
array: any[],
path: string,
options: PopulateOptions<PathType, AllType>
) {
if (options.use) {
const pathValues = array.map((item) => liteGet(item, path));
const convertedValues = options.use(pathValues);
populateEach<PathType>(array, path, options, convertedValues);
} else {
populateEach<PathType, AllType>(array, path, options);
}
}
/**
* Populate array (Async version)
* @param array
* @param path
* @param options
*/
export async function populateArrayAsync<PathType = any, AllType = any>(
array: any[],
path: string,
options: PopulateOptions<PathType, AllType>
) {
if (options.use) {
const pathValues = array.map((item) => liteGet(item, path));
const convertedValues = await options.use(pathValues);
await populateEachAsync<PathType>(array, path, options, convertedValues);
} else {
await populateEachAsync<PathType, AllType>(array, path, options);
}
}
/**
* Helper function to populate each item in the array
* @param array
* @param path
* @param options
* @param convertedValues
*/
function populateEach<PathType = any, AllType = any>(
array: any[],
path: string,
options: PopulateOptions<PathType, AllType>,
convertedValues?: AllType
) {
if (options.unique) {
const unique = {} as Record<string, PathType>;
for (const item of array) {
const pathValue = liteGet(item, path);
if (!["string", "number"].includes(typeof pathValue)) {
throw new Error(`Unique Path value must be a string or number`);
}
const uniqueKey = String(pathValue);
let convertedValue;
if (unique.hasOwnProperty(uniqueKey)) {
convertedValue = unique[uniqueKey];
} else {
convertedValue = options.each(pathValue, convertedValues!);
unique[uniqueKey] = convertedValue;
}
liteSet(item, options.as || path, convertedValue);
}
} else {
for (const item of array) {
const pathValue = liteGet(item, path);
liteSet(
item,
options.as ? options.as : path,
options.each(pathValue, convertedValues!)
);
}
}
}
/**
* populateArray (Async version)
* @param array
* @param path
* @param options
* @param convertedValues
*/
async function populateEachAsync<PathType = any, AllType = any>(
array: any[],
path: string,
options: PopulateOptions<PathType, AllType>,
convertedValues?: AllType
) {
if (options.unique) {
const unique = {} as Record<string, PathType>;
for (const item of array) {
const pathValue = liteGet(item, path);
if (!["string", "number"].includes(typeof pathValue)) {
throw new Error(`Unique Path value must be a string or number`);
}
const uniqueKey = String(pathValue);
let convertedValue;
if (unique.hasOwnProperty(uniqueKey)) {
convertedValue = unique[uniqueKey];
} else {
convertedValue = await options.each(pathValue, convertedValues!);
unique[uniqueKey] = convertedValue;
}
liteSet(item, options.as || path, convertedValue);
}
} else {
for (const item of array) {
const pathValue = liteGet(item, path);
liteSet(
item,
options.as ? options.as : path,
await options.each(pathValue, convertedValues!)
);
}
}
}