1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Tobias Koppers @sokra
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | /** @type {WeakMap<object, WeakMap<object, object>>} */
|
---|
9 | const mergeCache = new WeakMap();
|
---|
10 | /** @type {WeakMap<object, Map<string, Map<string|number|boolean, object>>>} */
|
---|
11 | const setPropertyCache = new WeakMap();
|
---|
12 | const DELETE = Symbol("DELETE");
|
---|
13 | const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
|
---|
14 |
|
---|
15 | /**
|
---|
16 | * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again.
|
---|
17 | * @template T
|
---|
18 | * @template O
|
---|
19 | * @example
|
---|
20 | * // performs cleverMerge(first, second), stores the result in WeakMap and returns result
|
---|
21 | * cachedCleverMerge({a: 1}, {a: 2})
|
---|
22 | * {a: 2}
|
---|
23 | * // when same arguments passed, gets the result from WeakMap and returns it.
|
---|
24 | * cachedCleverMerge({a: 1}, {a: 2})
|
---|
25 | * {a: 2}
|
---|
26 | * @param {T} first first object
|
---|
27 | * @param {O} second second object
|
---|
28 | * @returns {T & O | T | O} merged object of first and second object
|
---|
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 | * @template T
|
---|
50 | * @param {Partial<T>} obj object
|
---|
51 | * @param {string} property property
|
---|
52 | * @param {string|number|boolean} value assignment value
|
---|
53 | * @returns {T} new object
|
---|
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 | * @typedef {Object} ObjectParsedPropertyEntry
|
---|
85 | * @property {any | undefined} base base value
|
---|
86 | * @property {string | undefined} byProperty the name of the selector property
|
---|
87 | * @property {Map<string, any>} byValues value depending on selector property, merged with base
|
---|
88 | */
|
---|
89 |
|
---|
90 | /**
|
---|
91 | * @typedef {Object} ParsedObject
|
---|
92 | * @property {Map<string, ObjectParsedPropertyEntry>} static static properties (key is property name)
|
---|
93 | * @property {{ byProperty: string, fn: Function } | undefined} dynamic dynamic part
|
---|
94 | */
|
---|
95 |
|
---|
96 | /** @type {WeakMap<object, ParsedObject>} */
|
---|
97 | const parseCache = new WeakMap();
|
---|
98 |
|
---|
99 | /**
|
---|
100 | * @param {object} obj the object
|
---|
101 | * @returns {ParsedObject} parsed object
|
---|
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 | * @param {object} obj the object
|
---|
113 | * @returns {ParsedObject} parsed object
|
---|
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 | * @param {Map<string, ObjectParsedPropertyEntry>} info static properties (key is property name)
|
---|
183 | * @param {{ byProperty: string, fn: Function } | undefined} dynamicInfo dynamic part
|
---|
184 | * @returns {object} the object
|
---|
185 | */
|
---|
186 | const serializeObject = (info, dynamicInfo) => {
|
---|
187 | const obj = {};
|
---|
188 | // Setup byProperty structure
|
---|
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 | // Fill byProperty structure
|
---|
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 | * @param {any} value a single value
|
---|
224 | * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type
|
---|
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 | * Merges two objects. Objects are deeply clever merged.
|
---|
246 | * Arrays might reference the old value with "...".
|
---|
247 | * Non-object values take preference over object values.
|
---|
248 | * @template T
|
---|
249 | * @template O
|
---|
250 | * @param {T} first first object
|
---|
251 | * @param {O} second second object
|
---|
252 | * @returns {T & O | T | O} merged object of first and second object
|
---|
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 | * Merges two objects. Objects are deeply clever merged.
|
---|
265 | * @param {object} first first object
|
---|
266 | * @param {object} second second object
|
---|
267 | * @param {boolean} internalCaching should parsing of objects and nested merges be cached
|
---|
268 | * @returns {object} merged object of first and second object
|
---|
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 | // If the first argument has a dynamic part we modify the dynamic part to merge the second argument
|
---|
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 | // If the first part is static only, we merge the static parts and keep the dynamic part of the second argument
|
---|
297 | const secondObject = internalCaching
|
---|
298 | ? cachedParseObject(second)
|
---|
299 | : parseObject(second);
|
---|
300 | const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject;
|
---|
301 | /** @type {Map<string, ObjectParsedPropertyEntry>} */
|
---|
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 | * @param {ObjectParsedPropertyEntry} firstEntry a
|
---|
321 | * @param {ObjectParsedPropertyEntry} secondEntry b
|
---|
322 | * @param {boolean} internalCaching should parsing of objects and nested merges be cached
|
---|
323 | * @returns {ObjectParsedPropertyEntry} new entry
|
---|
324 | */
|
---|
325 | const mergeEntries = (firstEntry, secondEntry, internalCaching) => {
|
---|
326 | switch (getValueType(secondEntry.base)) {
|
---|
327 | case VALUE_TYPE_ATOM:
|
---|
328 | case VALUE_TYPE_DELETE:
|
---|
329 | // No need to consider firstEntry at all
|
---|
330 | // second value override everything
|
---|
331 | // = second.base + second.byProperty
|
---|
332 | return secondEntry;
|
---|
333 | case VALUE_TYPE_UNDEFINED:
|
---|
334 | if (!firstEntry.byProperty) {
|
---|
335 | // = first.base + second.byProperty
|
---|
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 | // = first.base + (first.byProperty + second.byProperty)
|
---|
347 | // need to merge first and second byValues
|
---|
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 | // The simple case
|
---|
365 | // = (first.base + second.base) + second.byProperty
|
---|
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 | // = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
|
---|
391 | newBase = mergeSingleValue(
|
---|
392 | firstEntry.base,
|
---|
393 | secondEntry.base,
|
---|
394 | internalCaching
|
---|
395 | );
|
---|
396 | } else {
|
---|
397 | // = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
|
---|
398 | newBase = firstEntry.base;
|
---|
399 | if (!intermediateByValues.has("default"))
|
---|
400 | intermediateByValues.set("default", secondEntry.base);
|
---|
401 | }
|
---|
402 | if (!secondEntry.byProperty) {
|
---|
403 | // = first.base + (first.byProperty + second.base)
|
---|
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 | * @param {Map<string, any>} byValues all values
|
---|
433 | * @param {string} key value of the selector
|
---|
434 | * @returns {any | undefined} value
|
---|
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 | * @param {any} a value
|
---|
445 | * @param {any} b value
|
---|
446 | * @param {boolean} internalCaching should parsing of objects and nested merges be cached
|
---|
447 | * @returns {any} value
|
---|
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 | * @template T
|
---|
502 | * @param {T} obj the object
|
---|
503 | * @returns {T} the object without operations like "..." or DELETE
|
---|
504 | */
|
---|
505 | const removeOperations = obj => {
|
---|
506 | const newObj = /** @type {T} */ ({});
|
---|
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 | * @template T
|
---|
530 | * @template {string} P
|
---|
531 | * @param {T} obj the object
|
---|
532 | * @param {P} byProperty the by description
|
---|
533 | * @param {...any} values values
|
---|
534 | * @returns {Omit<T, P>} object with merged byProperty
|
---|
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 } = /** @type {object} */ (obj);
|
---|
541 | const remaining = /** @type {T} */ (_remaining);
|
---|
542 | const byValue = /** @type {Record<string, T> | function(...any[]): T} */ (
|
---|
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 /** @type {T} */ (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;
|
---|