1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 | const mergeCache = new WeakMap();
|
10 |
|
11 | const setPropertyCache = new WeakMap();
|
12 | const DELETE = Symbol("DELETE");
|
13 | const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | const cachedCleverMerge = (first, second) => {
|
31 | if (second === undefined) return first;
|
32 | if (first === undefined) return second;
|
33 | if (typeof second !== "object" || second === null) return second;
|
34 | if (typeof first !== "object" || first === null) return first;
|
35 |
|
36 | let innerCache = mergeCache.get(first);
|
37 | if (innerCache === undefined) {
|
38 | innerCache = new WeakMap();
|
39 | mergeCache.set(first, innerCache);
|
40 | }
|
41 | const prevMerge = innerCache.get(second);
|
42 | if (prevMerge !== undefined) return prevMerge;
|
43 | const newMerge = _cleverMerge(first, second, true);
|
44 | innerCache.set(second, newMerge);
|
45 | return newMerge;
|
46 | };
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | const cachedSetProperty = (obj, property, value) => {
|
56 | let mapByProperty = setPropertyCache.get(obj);
|
57 |
|
58 | if (mapByProperty === undefined) {
|
59 | mapByProperty = new Map();
|
60 | setPropertyCache.set(obj, mapByProperty);
|
61 | }
|
62 |
|
63 | let mapByValue = mapByProperty.get(property);
|
64 |
|
65 | if (mapByValue === undefined) {
|
66 | mapByValue = new Map();
|
67 | mapByProperty.set(property, mapByValue);
|
68 | }
|
69 |
|
70 | let result = mapByValue.get(value);
|
71 |
|
72 | if (result) return result;
|
73 |
|
74 | result = {
|
75 | ...obj,
|
76 | [property]: value
|
77 | };
|
78 | mapByValue.set(value, result);
|
79 |
|
80 | return result;
|
81 | };
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | const parseCache = new WeakMap();
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | const cachedParseObject = obj => {
|
104 | const entry = parseCache.get(obj);
|
105 | if (entry !== undefined) return entry;
|
106 | const result = parseObject(obj);
|
107 | parseCache.set(obj, result);
|
108 | return result;
|
109 | };
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | const parseObject = obj => {
|
116 | const info = new Map();
|
117 | let dynamicInfo;
|
118 | const getInfo = p => {
|
119 | const entry = info.get(p);
|
120 | if (entry !== undefined) return entry;
|
121 | const newEntry = {
|
122 | base: undefined,
|
123 | byProperty: undefined,
|
124 | byValues: undefined
|
125 | };
|
126 | info.set(p, newEntry);
|
127 | return newEntry;
|
128 | };
|
129 | for (const key of Object.keys(obj)) {
|
130 | if (key.startsWith("by")) {
|
131 | const byProperty = key;
|
132 | const byObj = obj[byProperty];
|
133 | if (typeof byObj === "object") {
|
134 | for (const byValue of Object.keys(byObj)) {
|
135 | const obj = byObj[byValue];
|
136 | for (const key of Object.keys(obj)) {
|
137 | const entry = getInfo(key);
|
138 | if (entry.byProperty === undefined) {
|
139 | entry.byProperty = byProperty;
|
140 | entry.byValues = new Map();
|
141 | } else if (entry.byProperty !== byProperty) {
|
142 | throw new Error(
|
143 | `${byProperty} and ${entry.byProperty} for a single property is not supported`
|
144 | );
|
145 | }
|
146 | entry.byValues.set(byValue, obj[key]);
|
147 | if (byValue === "default") {
|
148 | for (const otherByValue of Object.keys(byObj)) {
|
149 | if (!entry.byValues.has(otherByValue))
|
150 | entry.byValues.set(otherByValue, undefined);
|
151 | }
|
152 | }
|
153 | }
|
154 | }
|
155 | } else if (typeof byObj === "function") {
|
156 | if (dynamicInfo === undefined) {
|
157 | dynamicInfo = {
|
158 | byProperty: key,
|
159 | fn: byObj
|
160 | };
|
161 | } else {
|
162 | throw new Error(
|
163 | `${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
|
164 | );
|
165 | }
|
166 | } else {
|
167 | const entry = getInfo(key);
|
168 | entry.base = obj[key];
|
169 | }
|
170 | } else {
|
171 | const entry = getInfo(key);
|
172 | entry.base = obj[key];
|
173 | }
|
174 | }
|
175 | return {
|
176 | static: info,
|
177 | dynamic: dynamicInfo
|
178 | };
|
179 | };
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | const serializeObject = (info, dynamicInfo) => {
|
187 | const obj = {};
|
188 |
|
189 | for (const entry of info.values()) {
|
190 | if (entry.byProperty !== undefined) {
|
191 | const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {});
|
192 | for (const byValue of entry.byValues.keys()) {
|
193 | byObj[byValue] = byObj[byValue] || {};
|
194 | }
|
195 | }
|
196 | }
|
197 | for (const [key, entry] of info) {
|
198 | if (entry.base !== undefined) {
|
199 | obj[key] = entry.base;
|
200 | }
|
201 |
|
202 | if (entry.byProperty !== undefined) {
|
203 | const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {});
|
204 | for (const byValue of Object.keys(byObj)) {
|
205 | const value = getFromByValues(entry.byValues, byValue);
|
206 | if (value !== undefined) byObj[byValue][key] = value;
|
207 | }
|
208 | }
|
209 | }
|
210 | if (dynamicInfo !== undefined) {
|
211 | obj[dynamicInfo.byProperty] = dynamicInfo.fn;
|
212 | }
|
213 | return obj;
|
214 | };
|
215 |
|
216 | const VALUE_TYPE_UNDEFINED = 0;
|
217 | const VALUE_TYPE_ATOM = 1;
|
218 | const VALUE_TYPE_ARRAY_EXTEND = 2;
|
219 | const VALUE_TYPE_OBJECT = 3;
|
220 | const VALUE_TYPE_DELETE = 4;
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | const getValueType = value => {
|
227 | if (value === undefined) {
|
228 | return VALUE_TYPE_UNDEFINED;
|
229 | } else if (value === DELETE) {
|
230 | return VALUE_TYPE_DELETE;
|
231 | } else if (Array.isArray(value)) {
|
232 | if (value.lastIndexOf("...") !== -1) return VALUE_TYPE_ARRAY_EXTEND;
|
233 | return VALUE_TYPE_ATOM;
|
234 | } else if (
|
235 | typeof value === "object" &&
|
236 | value !== null &&
|
237 | (!value.constructor || value.constructor === Object)
|
238 | ) {
|
239 | return VALUE_TYPE_OBJECT;
|
240 | }
|
241 | return VALUE_TYPE_ATOM;
|
242 | };
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | const cleverMerge = (first, second) => {
|
255 | if (second === undefined) return first;
|
256 | if (first === undefined) return second;
|
257 | if (typeof second !== "object" || second === null) return second;
|
258 | if (typeof first !== "object" || first === null) return first;
|
259 |
|
260 | return _cleverMerge(first, second, false);
|
261 | };
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | const _cleverMerge = (first, second, internalCaching = false) => {
|
271 | const firstObject = internalCaching
|
272 | ? cachedParseObject(first)
|
273 | : parseObject(first);
|
274 | const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject;
|
275 |
|
276 |
|
277 | if (firstDynamicInfo !== undefined) {
|
278 | let { byProperty, fn } = firstDynamicInfo;
|
279 | const fnInfo = fn[DYNAMIC_INFO];
|
280 | if (fnInfo) {
|
281 | second = internalCaching
|
282 | ? cachedCleverMerge(fnInfo[1], second)
|
283 | : cleverMerge(fnInfo[1], second);
|
284 | fn = fnInfo[0];
|
285 | }
|
286 | const newFn = (...args) => {
|
287 | const fnResult = fn(...args);
|
288 | return internalCaching
|
289 | ? cachedCleverMerge(fnResult, second)
|
290 | : cleverMerge(fnResult, second);
|
291 | };
|
292 | newFn[DYNAMIC_INFO] = [fn, second];
|
293 | return serializeObject(firstObject.static, { byProperty, fn: newFn });
|
294 | }
|
295 |
|
296 |
|
297 | const secondObject = internalCaching
|
298 | ? cachedParseObject(second)
|
299 | : parseObject(second);
|
300 | const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject;
|
301 |
|
302 | const resultInfo = new Map();
|
303 | for (const [key, firstEntry] of firstInfo) {
|
304 | const secondEntry = secondInfo.get(key);
|
305 | const entry =
|
306 | secondEntry !== undefined
|
307 | ? mergeEntries(firstEntry, secondEntry, internalCaching)
|
308 | : firstEntry;
|
309 | resultInfo.set(key, entry);
|
310 | }
|
311 | for (const [key, secondEntry] of secondInfo) {
|
312 | if (!firstInfo.has(key)) {
|
313 | resultInfo.set(key, secondEntry);
|
314 | }
|
315 | }
|
316 | return serializeObject(resultInfo, secondDynamicInfo);
|
317 | };
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 | const mergeEntries = (firstEntry, secondEntry, internalCaching) => {
|
326 | switch (getValueType(secondEntry.base)) {
|
327 | case VALUE_TYPE_ATOM:
|
328 | case VALUE_TYPE_DELETE:
|
329 |
|
330 |
|
331 |
|
332 | return secondEntry;
|
333 | case VALUE_TYPE_UNDEFINED:
|
334 | if (!firstEntry.byProperty) {
|
335 |
|
336 | return {
|
337 | base: firstEntry.base,
|
338 | byProperty: secondEntry.byProperty,
|
339 | byValues: secondEntry.byValues
|
340 | };
|
341 | } else if (firstEntry.byProperty !== secondEntry.byProperty) {
|
342 | throw new Error(
|
343 | `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
|
344 | );
|
345 | } else {
|
346 |
|
347 |
|
348 | const newByValues = new Map(firstEntry.byValues);
|
349 | for (const [key, value] of secondEntry.byValues) {
|
350 | const firstValue = getFromByValues(firstEntry.byValues, key);
|
351 | newByValues.set(
|
352 | key,
|
353 | mergeSingleValue(firstValue, value, internalCaching)
|
354 | );
|
355 | }
|
356 | return {
|
357 | base: firstEntry.base,
|
358 | byProperty: firstEntry.byProperty,
|
359 | byValues: newByValues
|
360 | };
|
361 | }
|
362 | default: {
|
363 | if (!firstEntry.byProperty) {
|
364 |
|
365 |
|
366 | return {
|
367 | base: mergeSingleValue(
|
368 | firstEntry.base,
|
369 | secondEntry.base,
|
370 | internalCaching
|
371 | ),
|
372 | byProperty: secondEntry.byProperty,
|
373 | byValues: secondEntry.byValues
|
374 | };
|
375 | }
|
376 | let newBase;
|
377 | const intermediateByValues = new Map(firstEntry.byValues);
|
378 | for (const [key, value] of intermediateByValues) {
|
379 | intermediateByValues.set(
|
380 | key,
|
381 | mergeSingleValue(value, secondEntry.base, internalCaching)
|
382 | );
|
383 | }
|
384 | if (
|
385 | Array.from(firstEntry.byValues.values()).every(value => {
|
386 | const type = getValueType(value);
|
387 | return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE;
|
388 | })
|
389 | ) {
|
390 |
|
391 | newBase = mergeSingleValue(
|
392 | firstEntry.base,
|
393 | secondEntry.base,
|
394 | internalCaching
|
395 | );
|
396 | } else {
|
397 |
|
398 | newBase = firstEntry.base;
|
399 | if (!intermediateByValues.has("default"))
|
400 | intermediateByValues.set("default", secondEntry.base);
|
401 | }
|
402 | if (!secondEntry.byProperty) {
|
403 |
|
404 | return {
|
405 | base: newBase,
|
406 | byProperty: firstEntry.byProperty,
|
407 | byValues: intermediateByValues
|
408 | };
|
409 | } else if (firstEntry.byProperty !== secondEntry.byProperty) {
|
410 | throw new Error(
|
411 | `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
|
412 | );
|
413 | }
|
414 | const newByValues = new Map(intermediateByValues);
|
415 | for (const [key, value] of secondEntry.byValues) {
|
416 | const firstValue = getFromByValues(intermediateByValues, key);
|
417 | newByValues.set(
|
418 | key,
|
419 | mergeSingleValue(firstValue, value, internalCaching)
|
420 | );
|
421 | }
|
422 | return {
|
423 | base: newBase,
|
424 | byProperty: firstEntry.byProperty,
|
425 | byValues: newByValues
|
426 | };
|
427 | }
|
428 | }
|
429 | };
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 | const getFromByValues = (byValues, key) => {
|
437 | if (key !== "default" && byValues.has(key)) {
|
438 | return byValues.get(key);
|
439 | }
|
440 | return byValues.get("default");
|
441 | };
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 | const mergeSingleValue = (a, b, internalCaching) => {
|
450 | const bType = getValueType(b);
|
451 | const aType = getValueType(a);
|
452 | switch (bType) {
|
453 | case VALUE_TYPE_DELETE:
|
454 | case VALUE_TYPE_ATOM:
|
455 | return b;
|
456 | case VALUE_TYPE_OBJECT: {
|
457 | return aType !== VALUE_TYPE_OBJECT
|
458 | ? b
|
459 | : internalCaching
|
460 | ? cachedCleverMerge(a, b)
|
461 | : cleverMerge(a, b);
|
462 | }
|
463 | case VALUE_TYPE_UNDEFINED:
|
464 | return a;
|
465 | case VALUE_TYPE_ARRAY_EXTEND:
|
466 | switch (
|
467 | aType !== VALUE_TYPE_ATOM
|
468 | ? aType
|
469 | : Array.isArray(a)
|
470 | ? VALUE_TYPE_ARRAY_EXTEND
|
471 | : VALUE_TYPE_OBJECT
|
472 | ) {
|
473 | case VALUE_TYPE_UNDEFINED:
|
474 | return b;
|
475 | case VALUE_TYPE_DELETE:
|
476 | return b.filter(item => item !== "...");
|
477 | case VALUE_TYPE_ARRAY_EXTEND: {
|
478 | const newArray = [];
|
479 | for (const item of b) {
|
480 | if (item === "...") {
|
481 | for (const item of a) {
|
482 | newArray.push(item);
|
483 | }
|
484 | } else {
|
485 | newArray.push(item);
|
486 | }
|
487 | }
|
488 | return newArray;
|
489 | }
|
490 | case VALUE_TYPE_OBJECT:
|
491 | return b.map(item => (item === "..." ? a : item));
|
492 | default:
|
493 | throw new Error("Not implemented");
|
494 | }
|
495 | default:
|
496 | throw new Error("Not implemented");
|
497 | }
|
498 | };
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 | const removeOperations = obj => {
|
506 | const newObj = ({});
|
507 | for (const key of Object.keys(obj)) {
|
508 | const value = obj[key];
|
509 | const type = getValueType(value);
|
510 | switch (type) {
|
511 | case VALUE_TYPE_UNDEFINED:
|
512 | case VALUE_TYPE_DELETE:
|
513 | break;
|
514 | case VALUE_TYPE_OBJECT:
|
515 | newObj[key] = removeOperations(value);
|
516 | break;
|
517 | case VALUE_TYPE_ARRAY_EXTEND:
|
518 | newObj[key] = value.filter(i => i !== "...");
|
519 | break;
|
520 | default:
|
521 | newObj[key] = value;
|
522 | break;
|
523 | }
|
524 | }
|
525 | return newObj;
|
526 | };
|
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 |
|
536 | const resolveByProperty = (obj, byProperty, ...values) => {
|
537 | if (typeof obj !== "object" || obj === null || !(byProperty in obj)) {
|
538 | return obj;
|
539 | }
|
540 | const { [byProperty]: _byValue, ..._remaining } = (obj);
|
541 | const remaining = (_remaining);
|
542 | const byValue = (
|
543 | _byValue
|
544 | );
|
545 | if (typeof byValue === "object") {
|
546 | const key = values[0];
|
547 | if (key in byValue) {
|
548 | return cachedCleverMerge(remaining, byValue[key]);
|
549 | } else if ("default" in byValue) {
|
550 | return cachedCleverMerge(remaining, byValue.default);
|
551 | } else {
|
552 | return (remaining);
|
553 | }
|
554 | } else if (typeof byValue === "function") {
|
555 | const result = byValue.apply(null, values);
|
556 | return cachedCleverMerge(
|
557 | remaining,
|
558 | resolveByProperty(result, byProperty, ...values)
|
559 | );
|
560 | }
|
561 | };
|
562 |
|
563 | exports.cachedSetProperty = cachedSetProperty;
|
564 | exports.cachedCleverMerge = cachedCleverMerge;
|
565 | exports.cleverMerge = cleverMerge;
|
566 | exports.resolveByProperty = resolveByProperty;
|
567 | exports.removeOperations = removeOperations;
|
568 | exports.DELETE = DELETE;
|