1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
|
9 | const { concatComparators, keepOriginalOrder } = require("../util/comparators");
|
10 | const smartGrouping = require("../util/smartGrouping");
|
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 | class StatsFactory {
|
36 | constructor() {
|
37 | this.hooks = Object.freeze({
|
38 |
|
39 | extract: new HookMap(
|
40 | () => new SyncBailHook(["object", "data", "context"])
|
41 | ),
|
42 |
|
43 | filter: new HookMap(
|
44 | () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
45 | ),
|
46 |
|
47 | sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
|
48 |
|
49 | filterSorted: new HookMap(
|
50 | () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
51 | ),
|
52 |
|
53 | groupResults: new HookMap(
|
54 | () => new SyncBailHook(["groupConfigs", "context"])
|
55 | ),
|
56 |
|
57 | sortResults: new HookMap(
|
58 | () => new SyncBailHook(["comparators", "context"])
|
59 | ),
|
60 |
|
61 | filterResults: new HookMap(
|
62 | () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
63 | ),
|
64 |
|
65 | merge: new HookMap(() => new SyncBailHook(["items", "context"])),
|
66 |
|
67 | result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
|
68 |
|
69 | getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
|
70 |
|
71 | getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
|
72 | });
|
73 | const hooks = this.hooks;
|
74 | this._caches =
|
75 | ({});
|
76 | for (const key of Object.keys(hooks)) {
|
77 | this._caches[key] = new Map();
|
78 | }
|
79 | this._inCreate = false;
|
80 | }
|
81 |
|
82 | _getAllLevelHooks(hookMap, cache, type) {
|
83 | const cacheEntry = cache.get(type);
|
84 | if (cacheEntry !== undefined) {
|
85 | return cacheEntry;
|
86 | }
|
87 | const hooks = [];
|
88 | const typeParts = type.split(".");
|
89 | for (let i = 0; i < typeParts.length; i++) {
|
90 | const hook = hookMap.get(typeParts.slice(i).join("."));
|
91 | if (hook) {
|
92 | hooks.push(hook);
|
93 | }
|
94 | }
|
95 | cache.set(type, hooks);
|
96 | return hooks;
|
97 | }
|
98 |
|
99 | _forEachLevel(hookMap, cache, type, fn) {
|
100 | for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
|
101 | const result = fn(hook);
|
102 | if (result !== undefined) return result;
|
103 | }
|
104 | }
|
105 |
|
106 | _forEachLevelWaterfall(hookMap, cache, type, data, fn) {
|
107 | for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
|
108 | data = fn(hook, data);
|
109 | }
|
110 | return data;
|
111 | }
|
112 |
|
113 | _forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) {
|
114 | const hooks = this._getAllLevelHooks(hookMap, cache, type);
|
115 | if (hooks.length === 0) return forceClone ? items.slice() : items;
|
116 | let i = 0;
|
117 | return items.filter((item, idx) => {
|
118 | for (const hook of hooks) {
|
119 | const r = fn(hook, item, idx, i);
|
120 | if (r !== undefined) {
|
121 | if (r) i++;
|
122 | return r;
|
123 | }
|
124 | }
|
125 | i++;
|
126 | return true;
|
127 | });
|
128 | }
|
129 |
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | create(type, data, baseContext) {
|
137 | if (this._inCreate) {
|
138 | return this._create(type, data, baseContext);
|
139 | } else {
|
140 | try {
|
141 | this._inCreate = true;
|
142 | return this._create(type, data, baseContext);
|
143 | } finally {
|
144 | for (const key of Object.keys(this._caches)) this._caches[key].clear();
|
145 | this._inCreate = false;
|
146 | }
|
147 | }
|
148 | }
|
149 |
|
150 | _create(type, data, baseContext) {
|
151 | const context = {
|
152 | ...baseContext,
|
153 | type,
|
154 | [type]: data
|
155 | };
|
156 | if (Array.isArray(data)) {
|
157 |
|
158 | const items = this._forEachLevelFilter(
|
159 | this.hooks.filter,
|
160 | this._caches.filter,
|
161 | type,
|
162 | data,
|
163 | (h, r, idx, i) => h.call(r, context, idx, i),
|
164 | true
|
165 | );
|
166 |
|
167 |
|
168 | const comparators = [];
|
169 | this._forEachLevel(this.hooks.sort, this._caches.sort, type, h =>
|
170 | h.call(comparators, context)
|
171 | );
|
172 | if (comparators.length > 0) {
|
173 | items.sort(
|
174 |
|
175 | concatComparators(...comparators, keepOriginalOrder(items))
|
176 | );
|
177 | }
|
178 |
|
179 |
|
180 | const items2 = this._forEachLevelFilter(
|
181 | this.hooks.filterSorted,
|
182 | this._caches.filterSorted,
|
183 | type,
|
184 | items,
|
185 | (h, r, idx, i) => h.call(r, context, idx, i),
|
186 | false
|
187 | );
|
188 |
|
189 |
|
190 | let resultItems = items2.map((item, i) => {
|
191 | const itemContext = {
|
192 | ...context,
|
193 | _index: i
|
194 | };
|
195 |
|
196 |
|
197 | const itemName = this._forEachLevel(
|
198 | this.hooks.getItemName,
|
199 | this._caches.getItemName,
|
200 | `${type}[]`,
|
201 | h => h.call(item, itemContext)
|
202 | );
|
203 | if (itemName) itemContext[itemName] = item;
|
204 | const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;
|
205 |
|
206 |
|
207 | const itemFactory =
|
208 | this._forEachLevel(
|
209 | this.hooks.getItemFactory,
|
210 | this._caches.getItemFactory,
|
211 | innerType,
|
212 | h => h.call(item, itemContext)
|
213 | ) || this;
|
214 |
|
215 |
|
216 | return itemFactory.create(innerType, item, itemContext);
|
217 | });
|
218 |
|
219 |
|
220 | const comparators2 = [];
|
221 | this._forEachLevel(
|
222 | this.hooks.sortResults,
|
223 | this._caches.sortResults,
|
224 | type,
|
225 | h => h.call(comparators2, context)
|
226 | );
|
227 | if (comparators2.length > 0) {
|
228 | resultItems.sort(
|
229 |
|
230 | concatComparators(...comparators2, keepOriginalOrder(resultItems))
|
231 | );
|
232 | }
|
233 |
|
234 |
|
235 | const groupConfigs = [];
|
236 | this._forEachLevel(
|
237 | this.hooks.groupResults,
|
238 | this._caches.groupResults,
|
239 | type,
|
240 | h => h.call(groupConfigs, context)
|
241 | );
|
242 | if (groupConfigs.length > 0) {
|
243 | resultItems = smartGrouping(resultItems, groupConfigs);
|
244 | }
|
245 |
|
246 |
|
247 | const finalResultItems = this._forEachLevelFilter(
|
248 | this.hooks.filterResults,
|
249 | this._caches.filterResults,
|
250 | type,
|
251 | resultItems,
|
252 | (h, r, idx, i) => h.call(r, context, idx, i),
|
253 | false
|
254 | );
|
255 |
|
256 |
|
257 | let result = this._forEachLevel(
|
258 | this.hooks.merge,
|
259 | this._caches.merge,
|
260 | type,
|
261 | h => h.call(finalResultItems, context)
|
262 | );
|
263 | if (result === undefined) result = finalResultItems;
|
264 |
|
265 |
|
266 | return this._forEachLevelWaterfall(
|
267 | this.hooks.result,
|
268 | this._caches.result,
|
269 | type,
|
270 | result,
|
271 | (h, r) => h.call(r, context)
|
272 | );
|
273 | } else {
|
274 | const object = {};
|
275 |
|
276 |
|
277 | this._forEachLevel(this.hooks.extract, this._caches.extract, type, h =>
|
278 | h.call(object, data, context)
|
279 | );
|
280 |
|
281 |
|
282 | return this._forEachLevelWaterfall(
|
283 | this.hooks.result,
|
284 | this._caches.result,
|
285 | type,
|
286 | object,
|
287 | (h, r) => h.call(r, context)
|
288 | );
|
289 | }
|
290 | }
|
291 | }
|
292 | module.exports = StatsFactory;
|