| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  | /** @type {WeakMap<object, WeakMap<object, object>>} */ | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | const mergeCache = new WeakMap(); | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  | /** @type {WeakMap<object, Map<string, Map<string|number|boolean, object>>>} */ | 
					
						
							|  |  |  | const setPropertyCache = new WeakMap(); | 
					
						
							| 
									
										
										
										
											2020-05-28 22:52:30 +08:00
										 |  |  | const DELETE = Symbol("DELETE"); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | const DYNAMIC_INFO = Symbol("cleverMerge dynamic info"); | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-10-13 02:16:58 +08:00
										 |  |  |  * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again. | 
					
						
							| 
									
										
										
										
											2020-06-18 04:17:14 +08:00
										 |  |  |  * @template T | 
					
						
							|  |  |  |  * @template O | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  |  * @example | 
					
						
							|  |  |  |  * // performs cleverMerge(first, second), stores the result in WeakMap and returns result
 | 
					
						
							|  |  |  |  * cachedCleverMerge({a: 1}, {a: 2}) | 
					
						
							|  |  |  |  * {a: 2} | 
					
						
							|  |  |  |  *  // when same arguments passed, gets the result from WeakMap and returns it.
 | 
					
						
							|  |  |  |  * cachedCleverMerge({a: 1}, {a: 2}) | 
					
						
							|  |  |  |  * {a: 2} | 
					
						
							| 
									
										
										
										
											2020-06-18 04:17:14 +08:00
										 |  |  |  * @param {T} first first object | 
					
						
							|  |  |  |  * @param {O} second second object | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  |  * @returns {T & O | T | O} merged object of first and second object | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const cachedCleverMerge = (first, second) => { | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 	if (second === undefined) return first; | 
					
						
							|  |  |  | 	if (first === undefined) return second; | 
					
						
							|  |  |  | 	if (typeof second !== "object" || second === null) return second; | 
					
						
							|  |  |  | 	if (typeof first !== "object" || first === null) return first; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 	let innerCache = mergeCache.get(first); | 
					
						
							|  |  |  | 	if (innerCache === undefined) { | 
					
						
							|  |  |  | 		innerCache = new WeakMap(); | 
					
						
							|  |  |  | 		mergeCache.set(first, innerCache); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const prevMerge = innerCache.get(second); | 
					
						
							|  |  |  | 	if (prevMerge !== undefined) return prevMerge; | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 	const newMerge = _cleverMerge(first, second, true); | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 	innerCache.set(second, newMerge); | 
					
						
							|  |  |  | 	return newMerge; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-06-18 04:17:14 +08:00
										 |  |  |  * @template T | 
					
						
							|  |  |  |  * @param {Partial<T>} obj object | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  |  * @param {string} property property | 
					
						
							|  |  |  |  * @param {string|number|boolean} value assignment value | 
					
						
							| 
									
										
										
										
											2020-06-18 04:17:14 +08:00
										 |  |  |  * @returns {T} new object | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const cachedSetProperty = (obj, property, value) => { | 
					
						
							|  |  |  | 	let mapByProperty = setPropertyCache.get(obj); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (mapByProperty === undefined) { | 
					
						
							|  |  |  | 		mapByProperty = new Map(); | 
					
						
							|  |  |  | 		setPropertyCache.set(obj, mapByProperty); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	let mapByValue = mapByProperty.get(property); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (mapByValue === undefined) { | 
					
						
							|  |  |  | 		mapByValue = new Map(); | 
					
						
							|  |  |  | 		mapByProperty.set(property, mapByValue); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	let result = mapByValue.get(value); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (result) return result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	result = { | 
					
						
							|  |  |  | 		...obj, | 
					
						
							|  |  |  | 		[property]: value | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	mapByValue.set(value, result); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {Object} ObjectParsedPropertyEntry | 
					
						
							|  |  |  |  * @property {any | undefined} base base value | 
					
						
							|  |  |  |  * @property {string | undefined} byProperty the name of the selector property | 
					
						
							|  |  |  |  * @property {Map<string, any>} byValues value depending on selector property, merged with base | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {Object} ParsedObject | 
					
						
							|  |  |  |  * @property {Map<string, ObjectParsedPropertyEntry>} static static properties (key is property name) | 
					
						
							|  |  |  |  * @property {{ byProperty: string, fn: Function } | undefined} dynamic dynamic part | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @type {WeakMap<object, ParsedObject>} */ | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | const parseCache = new WeakMap(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  * @param {object} obj the object | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  |  * @returns {ParsedObject} parsed object | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const cachedParseObject = obj => { | 
					
						
							|  |  |  | 	const entry = parseCache.get(obj); | 
					
						
							|  |  |  | 	if (entry !== undefined) return entry; | 
					
						
							|  |  |  | 	const result = parseObject(obj); | 
					
						
							|  |  |  | 	parseCache.set(obj, result); | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {object} obj the object | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  |  * @returns {ParsedObject} parsed object | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const parseObject = obj => { | 
					
						
							|  |  |  | 	const info = new Map(); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	let dynamicInfo; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	const getInfo = p => { | 
					
						
							|  |  |  | 		const entry = info.get(p); | 
					
						
							|  |  |  | 		if (entry !== undefined) return entry; | 
					
						
							|  |  |  | 		const newEntry = { | 
					
						
							|  |  |  | 			base: undefined, | 
					
						
							|  |  |  | 			byProperty: undefined, | 
					
						
							|  |  |  | 			byValues: undefined | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		info.set(p, newEntry); | 
					
						
							|  |  |  | 		return newEntry; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	for (const key of Object.keys(obj)) { | 
					
						
							|  |  |  | 		if (key.startsWith("by")) { | 
					
						
							|  |  |  | 			const byProperty = key; | 
					
						
							|  |  |  | 			const byObj = obj[byProperty]; | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 			if (typeof byObj === "object") { | 
					
						
							|  |  |  | 				for (const byValue of Object.keys(byObj)) { | 
					
						
							|  |  |  | 					const obj = byObj[byValue]; | 
					
						
							|  |  |  | 					for (const key of Object.keys(obj)) { | 
					
						
							|  |  |  | 						const entry = getInfo(key); | 
					
						
							|  |  |  | 						if (entry.byProperty === undefined) { | 
					
						
							|  |  |  | 							entry.byProperty = byProperty; | 
					
						
							|  |  |  | 							entry.byValues = new Map(); | 
					
						
							|  |  |  | 						} else if (entry.byProperty !== byProperty) { | 
					
						
							|  |  |  | 							throw new Error( | 
					
						
							|  |  |  | 								`${byProperty} and ${entry.byProperty} for a single property is not supported` | 
					
						
							|  |  |  | 							); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						entry.byValues.set(byValue, obj[key]); | 
					
						
							|  |  |  | 						if (byValue === "default") { | 
					
						
							|  |  |  | 							for (const otherByValue of Object.keys(byObj)) { | 
					
						
							|  |  |  | 								if (!entry.byValues.has(otherByValue)) | 
					
						
							|  |  |  | 									entry.byValues.set(otherByValue, undefined); | 
					
						
							|  |  |  | 							} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 			} else if (typeof byObj === "function") { | 
					
						
							|  |  |  | 				if (dynamicInfo === undefined) { | 
					
						
							|  |  |  | 					dynamicInfo = { | 
					
						
							|  |  |  | 						byProperty: key, | 
					
						
							|  |  |  | 						fn: byObj | 
					
						
							|  |  |  | 					}; | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					throw new Error( | 
					
						
							|  |  |  | 						`${key} and ${dynamicInfo.byProperty} when both are functions is not supported` | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				const entry = getInfo(key); | 
					
						
							|  |  |  | 				entry.base = obj[key]; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			const entry = getInfo(key); | 
					
						
							|  |  |  | 			entry.base = obj[key]; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	return { | 
					
						
							|  |  |  | 		static: info, | 
					
						
							|  |  |  | 		dynamic: dynamicInfo | 
					
						
							|  |  |  | 	}; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  |  * @param {Map<string, ObjectParsedPropertyEntry>} info static properties (key is property name) | 
					
						
							|  |  |  |  * @param {{ byProperty: string, fn: Function } | undefined} dynamicInfo dynamic part | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  * @returns {object} the object | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | const serializeObject = (info, dynamicInfo) => { | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	const obj = {}; | 
					
						
							|  |  |  | 	// Setup byProperty structure
 | 
					
						
							|  |  |  | 	for (const entry of info.values()) { | 
					
						
							|  |  |  | 		if (entry.byProperty !== undefined) { | 
					
						
							|  |  |  | 			const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); | 
					
						
							|  |  |  | 			for (const byValue of entry.byValues.keys()) { | 
					
						
							|  |  |  | 				byObj[byValue] = byObj[byValue] || {}; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for (const [key, entry] of info) { | 
					
						
							|  |  |  | 		if (entry.base !== undefined) { | 
					
						
							|  |  |  | 			obj[key] = entry.base; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Fill byProperty structure
 | 
					
						
							|  |  |  | 		if (entry.byProperty !== undefined) { | 
					
						
							|  |  |  | 			const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); | 
					
						
							|  |  |  | 			for (const byValue of Object.keys(byObj)) { | 
					
						
							|  |  |  | 				const value = getFromByValues(entry.byValues, byValue); | 
					
						
							|  |  |  | 				if (value !== undefined) byObj[byValue][key] = value; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	if (dynamicInfo !== undefined) { | 
					
						
							|  |  |  | 		obj[dynamicInfo.byProperty] = dynamicInfo.fn; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	return obj; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const VALUE_TYPE_UNDEFINED = 0; | 
					
						
							|  |  |  | const VALUE_TYPE_ATOM = 1; | 
					
						
							|  |  |  | const VALUE_TYPE_ARRAY_EXTEND = 2; | 
					
						
							|  |  |  | const VALUE_TYPE_OBJECT = 3; | 
					
						
							|  |  |  | const VALUE_TYPE_DELETE = 4; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {any} value a single value | 
					
						
							|  |  |  |  * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const getValueType = value => { | 
					
						
							|  |  |  | 	if (value === undefined) { | 
					
						
							|  |  |  | 		return VALUE_TYPE_UNDEFINED; | 
					
						
							|  |  |  | 	} else if (value === DELETE) { | 
					
						
							|  |  |  | 		return VALUE_TYPE_DELETE; | 
					
						
							|  |  |  | 	} else if (Array.isArray(value)) { | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 		if (value.lastIndexOf("...") !== -1) return VALUE_TYPE_ARRAY_EXTEND; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 		return VALUE_TYPE_ATOM; | 
					
						
							|  |  |  | 	} else if ( | 
					
						
							|  |  |  | 		typeof value === "object" && | 
					
						
							|  |  |  | 		value !== null && | 
					
						
							|  |  |  | 		(!value.constructor || value.constructor === Object) | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		return VALUE_TYPE_OBJECT; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return VALUE_TYPE_ATOM; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Merges two objects. Objects are deeply clever merged. | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  |  * Arrays might reference the old value with "...". | 
					
						
							|  |  |  |  * Non-object values take preference over object values. | 
					
						
							|  |  |  |  * @template T | 
					
						
							|  |  |  |  * @template O | 
					
						
							|  |  |  |  * @param {T} first first object | 
					
						
							|  |  |  |  * @param {O} second second object | 
					
						
							|  |  |  |  * @returns {T & O | T | O} merged object of first and second object | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const cleverMerge = (first, second) => { | 
					
						
							|  |  |  | 	if (second === undefined) return first; | 
					
						
							|  |  |  | 	if (first === undefined) return second; | 
					
						
							|  |  |  | 	if (typeof second !== "object" || second === null) return second; | 
					
						
							|  |  |  | 	if (typeof first !== "object" || first === null) return first; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return _cleverMerge(first, second, false); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Merges two objects. Objects are deeply clever merged. | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  |  * @param {object} first first object | 
					
						
							|  |  |  |  * @param {object} second second object | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  * @param {boolean} internalCaching should parsing of objects and nested merges be cached | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  |  * @returns {object} merged object of first and second object | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | const _cleverMerge = (first, second, internalCaching = false) => { | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	const firstObject = internalCaching | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 		? cachedParseObject(first) | 
					
						
							|  |  |  | 		: parseObject(first); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the first argument has a dynamic part we modify the dynamic part to merge the second argument
 | 
					
						
							|  |  |  | 	if (firstDynamicInfo !== undefined) { | 
					
						
							|  |  |  | 		let { byProperty, fn } = firstDynamicInfo; | 
					
						
							|  |  |  | 		const fnInfo = fn[DYNAMIC_INFO]; | 
					
						
							|  |  |  | 		if (fnInfo) { | 
					
						
							|  |  |  | 			second = internalCaching | 
					
						
							|  |  |  | 				? cachedCleverMerge(fnInfo[1], second) | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 				: cleverMerge(fnInfo[1], second); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 			fn = fnInfo[0]; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		const newFn = (...args) => { | 
					
						
							|  |  |  | 			const fnResult = fn(...args); | 
					
						
							|  |  |  | 			return internalCaching | 
					
						
							|  |  |  | 				? cachedCleverMerge(fnResult, second) | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 				: cleverMerge(fnResult, second); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 		}; | 
					
						
							|  |  |  | 		newFn[DYNAMIC_INFO] = [fn, second]; | 
					
						
							|  |  |  | 		return serializeObject(firstObject.static, { byProperty, fn: newFn }); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the first part is static only, we merge the static parts and keep the dynamic part of the second argument
 | 
					
						
							|  |  |  | 	const secondObject = internalCaching | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 		? cachedParseObject(second) | 
					
						
							|  |  |  | 		: parseObject(second); | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	/** @type {Map<string, ObjectParsedPropertyEntry>} */ | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	const resultInfo = new Map(); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	for (const [key, firstEntry] of firstInfo) { | 
					
						
							|  |  |  | 		const secondEntry = secondInfo.get(key); | 
					
						
							|  |  |  | 		const entry = | 
					
						
							|  |  |  | 			secondEntry !== undefined | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 				? mergeEntries(firstEntry, secondEntry, internalCaching) | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 				: firstEntry; | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 		resultInfo.set(key, entry); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for (const [key, secondEntry] of secondInfo) { | 
					
						
							|  |  |  | 		if (!firstInfo.has(key)) { | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 			resultInfo.set(key, secondEntry); | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 	return serializeObject(resultInfo, secondDynamicInfo); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {ObjectParsedPropertyEntry} firstEntry a | 
					
						
							|  |  |  |  * @param {ObjectParsedPropertyEntry} secondEntry b | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  * @param {boolean} internalCaching should parsing of objects and nested merges be cached | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  |  * @returns {ObjectParsedPropertyEntry} new entry | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | const mergeEntries = (firstEntry, secondEntry, internalCaching) => { | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	switch (getValueType(secondEntry.base)) { | 
					
						
							|  |  |  | 		case VALUE_TYPE_ATOM: | 
					
						
							|  |  |  | 		case VALUE_TYPE_DELETE: | 
					
						
							|  |  |  | 			// No need to consider firstEntry at all
 | 
					
						
							|  |  |  | 			// second value override everything
 | 
					
						
							|  |  |  | 			// = second.base + second.byProperty
 | 
					
						
							|  |  |  | 			return secondEntry; | 
					
						
							|  |  |  | 		case VALUE_TYPE_UNDEFINED: | 
					
						
							|  |  |  | 			if (!firstEntry.byProperty) { | 
					
						
							|  |  |  | 				// = first.base + second.byProperty
 | 
					
						
							|  |  |  | 				return { | 
					
						
							|  |  |  | 					base: firstEntry.base, | 
					
						
							|  |  |  | 					byProperty: secondEntry.byProperty, | 
					
						
							|  |  |  | 					byValues: secondEntry.byValues | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} else if (firstEntry.byProperty !== secondEntry.byProperty) { | 
					
						
							|  |  |  | 				throw new Error( | 
					
						
							|  |  |  | 					`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				// = first.base + (first.byProperty + second.byProperty)
 | 
					
						
							|  |  |  | 				// need to merge first and second byValues
 | 
					
						
							|  |  |  | 				const newByValues = new Map(firstEntry.byValues); | 
					
						
							|  |  |  | 				for (const [key, value] of secondEntry.byValues) { | 
					
						
							|  |  |  | 					const firstValue = getFromByValues(firstEntry.byValues, key); | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 					newByValues.set( | 
					
						
							|  |  |  | 						key, | 
					
						
							|  |  |  | 						mergeSingleValue(firstValue, value, internalCaching) | 
					
						
							|  |  |  | 					); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				return { | 
					
						
							|  |  |  | 					base: firstEntry.base, | 
					
						
							|  |  |  | 					byProperty: firstEntry.byProperty, | 
					
						
							|  |  |  | 					byValues: newByValues | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		default: { | 
					
						
							|  |  |  | 			if (!firstEntry.byProperty) { | 
					
						
							|  |  |  | 				// The simple case
 | 
					
						
							|  |  |  | 				// = (first.base + second.base) + second.byProperty
 | 
					
						
							|  |  |  | 				return { | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 					base: mergeSingleValue( | 
					
						
							|  |  |  | 						firstEntry.base, | 
					
						
							|  |  |  | 						secondEntry.base, | 
					
						
							|  |  |  | 						internalCaching | 
					
						
							|  |  |  | 					), | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 					byProperty: secondEntry.byProperty, | 
					
						
							|  |  |  | 					byValues: secondEntry.byValues | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			let newBase; | 
					
						
							|  |  |  | 			const intermediateByValues = new Map(firstEntry.byValues); | 
					
						
							|  |  |  | 			for (const [key, value] of intermediateByValues) { | 
					
						
							|  |  |  | 				intermediateByValues.set( | 
					
						
							|  |  |  | 					key, | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 					mergeSingleValue(value, secondEntry.base, internalCaching) | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if ( | 
					
						
							|  |  |  | 				Array.from(firstEntry.byValues.values()).every(value => { | 
					
						
							|  |  |  | 					const type = getValueType(value); | 
					
						
							|  |  |  | 					return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE; | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			) { | 
					
						
							|  |  |  | 				// = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
 | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 				newBase = mergeSingleValue( | 
					
						
							|  |  |  | 					firstEntry.base, | 
					
						
							|  |  |  | 					secondEntry.base, | 
					
						
							|  |  |  | 					internalCaching | 
					
						
							|  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				// = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
 | 
					
						
							|  |  |  | 				newBase = firstEntry.base; | 
					
						
							|  |  |  | 				if (!intermediateByValues.has("default")) | 
					
						
							|  |  |  | 					intermediateByValues.set("default", secondEntry.base); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (!secondEntry.byProperty) { | 
					
						
							|  |  |  | 				// = first.base + (first.byProperty + second.base)
 | 
					
						
							|  |  |  | 				return { | 
					
						
							|  |  |  | 					base: newBase, | 
					
						
							|  |  |  | 					byProperty: firstEntry.byProperty, | 
					
						
							|  |  |  | 					byValues: intermediateByValues | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} else if (firstEntry.byProperty !== secondEntry.byProperty) { | 
					
						
							|  |  |  | 				throw new Error( | 
					
						
							|  |  |  | 					`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const newByValues = new Map(intermediateByValues); | 
					
						
							|  |  |  | 			for (const [key, value] of secondEntry.byValues) { | 
					
						
							|  |  |  | 				const firstValue = getFromByValues(intermediateByValues, key); | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 				newByValues.set( | 
					
						
							|  |  |  | 					key, | 
					
						
							|  |  |  | 					mergeSingleValue(firstValue, value, internalCaching) | 
					
						
							|  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				base: newBase, | 
					
						
							|  |  |  | 				byProperty: firstEntry.byProperty, | 
					
						
							|  |  |  | 				byValues: newByValues | 
					
						
							|  |  |  | 			}; | 
					
						
							| 
									
										
										
										
											2020-05-28 22:52:30 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {Map<string, any>} byValues all values | 
					
						
							|  |  |  |  * @param {string} key value of the selector | 
					
						
							|  |  |  |  * @returns {any | undefined} value | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const getFromByValues = (byValues, key) => { | 
					
						
							|  |  |  | 	if (key !== "default" && byValues.has(key)) { | 
					
						
							|  |  |  | 		return byValues.get(key); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return byValues.get("default"); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {any} a value | 
					
						
							|  |  |  |  * @param {any} b value | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  |  * @param {boolean} internalCaching should parsing of objects and nested merges be cached | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  |  * @returns {any} value | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | const mergeSingleValue = (a, b, internalCaching) => { | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	const bType = getValueType(b); | 
					
						
							|  |  |  | 	const aType = getValueType(a); | 
					
						
							|  |  |  | 	switch (bType) { | 
					
						
							|  |  |  | 		case VALUE_TYPE_DELETE: | 
					
						
							|  |  |  | 		case VALUE_TYPE_ATOM: | 
					
						
							|  |  |  | 			return b; | 
					
						
							|  |  |  | 		case VALUE_TYPE_OBJECT: { | 
					
						
							| 
									
										
										
										
											2020-07-13 20:28:55 +08:00
										 |  |  | 			return aType !== VALUE_TYPE_OBJECT | 
					
						
							|  |  |  | 				? b | 
					
						
							|  |  |  | 				: internalCaching | 
					
						
							|  |  |  | 				? cachedCleverMerge(a, b) | 
					
						
							|  |  |  | 				: cleverMerge(a, b); | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 		case VALUE_TYPE_UNDEFINED: | 
					
						
							|  |  |  | 			return a; | 
					
						
							|  |  |  | 		case VALUE_TYPE_ARRAY_EXTEND: | 
					
						
							|  |  |  | 			switch ( | 
					
						
							|  |  |  | 				aType !== VALUE_TYPE_ATOM | 
					
						
							|  |  |  | 					? aType | 
					
						
							|  |  |  | 					: Array.isArray(a) | 
					
						
							|  |  |  | 					? VALUE_TYPE_ARRAY_EXTEND | 
					
						
							|  |  |  | 					: VALUE_TYPE_OBJECT | 
					
						
							|  |  |  | 			) { | 
					
						
							|  |  |  | 				case VALUE_TYPE_UNDEFINED: | 
					
						
							|  |  |  | 					return b; | 
					
						
							|  |  |  | 				case VALUE_TYPE_DELETE: | 
					
						
							|  |  |  | 					return b.filter(item => item !== "..."); | 
					
						
							|  |  |  | 				case VALUE_TYPE_ARRAY_EXTEND: { | 
					
						
							|  |  |  | 					const newArray = []; | 
					
						
							|  |  |  | 					for (const item of b) { | 
					
						
							|  |  |  | 						if (item === "...") { | 
					
						
							|  |  |  | 							for (const item of a) { | 
					
						
							|  |  |  | 								newArray.push(item); | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} else { | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 							newArray.push(item); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 					return newArray; | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 				case VALUE_TYPE_OBJECT: | 
					
						
							|  |  |  | 					return b.map(item => (item === "..." ? a : item)); | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					throw new Error("Not implemented"); | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			throw new Error("Not implemented"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  |  * @template T | 
					
						
							|  |  |  |  * @param {T} obj the object | 
					
						
							|  |  |  |  * @returns {T} the object without operations like "..." or DELETE | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const removeOperations = obj => { | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  | 	const newObj = /** @type {T} */ ({}); | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	for (const key of Object.keys(obj)) { | 
					
						
							|  |  |  | 		const value = obj[key]; | 
					
						
							|  |  |  | 		const type = getValueType(value); | 
					
						
							|  |  |  | 		switch (type) { | 
					
						
							|  |  |  | 			case VALUE_TYPE_UNDEFINED: | 
					
						
							|  |  |  | 			case VALUE_TYPE_DELETE: | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			case VALUE_TYPE_OBJECT: | 
					
						
							|  |  |  | 				newObj[key] = removeOperations(value); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			case VALUE_TYPE_ARRAY_EXTEND: | 
					
						
							|  |  |  | 				newObj[key] = value.filter(i => i !== "..."); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				newObj[key] = value; | 
					
						
							|  |  |  | 				break; | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | 	return newObj; | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  |  * @template T | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  |  * @template {string} P | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  |  * @param {T} obj the object | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  |  * @param {P} byProperty the by description | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  |  * @param  {...any} values values | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  |  * @returns {Omit<T, P>} object with merged byProperty | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const resolveByProperty = (obj, byProperty, ...values) => { | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 	if (typeof obj !== "object" || obj === null || !(byProperty in obj)) { | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 		return obj; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-01-06 21:44:32 +08:00
										 |  |  | 	const { [byProperty]: _byValue, ..._remaining } = /** @type {object} */ (obj); | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 	const remaining = /** @type {T} */ (_remaining); | 
					
						
							| 
									
										
										
										
											2021-05-11 15:31:46 +08:00
										 |  |  | 	const byValue = /** @type {Record<string, T> | function(...any[]): T} */ ( | 
					
						
							|  |  |  | 		_byValue | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 	if (typeof byValue === "object") { | 
					
						
							|  |  |  | 		const key = values[0]; | 
					
						
							|  |  |  | 		if (key in byValue) { | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 			return cachedCleverMerge(remaining, byValue[key]); | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 		} else if ("default" in byValue) { | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 			return cachedCleverMerge(remaining, byValue.default); | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 			return /** @type {T} */ (remaining); | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} else if (typeof byValue === "function") { | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 		const result = byValue.apply(null, values); | 
					
						
							| 
									
										
										
										
											2021-01-06 17:51:44 +08:00
										 |  |  | 		return cachedCleverMerge( | 
					
						
							| 
									
										
										
										
											2021-01-05 18:29:54 +08:00
										 |  |  | 			remaining, | 
					
						
							|  |  |  | 			resolveByProperty(result, byProperty, ...values) | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 06:32:24 +08:00
										 |  |  | exports.cachedSetProperty = cachedSetProperty; | 
					
						
							| 
									
										
										
										
											2019-05-13 21:16:23 +08:00
										 |  |  | exports.cachedCleverMerge = cachedCleverMerge; | 
					
						
							|  |  |  | exports.cleverMerge = cleverMerge; | 
					
						
							| 
									
										
										
										
											2020-12-30 17:42:11 +08:00
										 |  |  | exports.resolveByProperty = resolveByProperty; | 
					
						
							| 
									
										
										
										
											2020-07-11 16:30:18 +08:00
										 |  |  | exports.removeOperations = removeOperations; | 
					
						
							| 
									
										
										
										
											2020-05-28 22:52:30 +08:00
										 |  |  | exports.DELETE = DELETE; |