cleverMerge.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /** @type {WeakMap<EXPECTED_OBJECT, WeakMap<EXPECTED_OBJECT, EXPECTED_OBJECT>>} */
  7. const mergeCache = new WeakMap();
  8. /** @typedef {Map<string, Map<string | number | boolean, EXPECTED_OBJECT>>} InnerPropertyCache */
  9. /** @type {WeakMap<EXPECTED_OBJECT, InnerPropertyCache>} */
  10. const setPropertyCache = new WeakMap();
  11. const DELETE = Symbol("DELETE");
  12. const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
  13. /**
  14. * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again.
  15. * @template T
  16. * @template O
  17. * @example
  18. * // performs cleverMerge(first, second), stores the result in WeakMap and returns result
  19. * cachedCleverMerge({a: 1}, {a: 2})
  20. * {a: 2}
  21. * // when same arguments passed, gets the result from WeakMap and returns it.
  22. * cachedCleverMerge({a: 1}, {a: 2})
  23. * {a: 2}
  24. * @param {T | null | undefined} first first object
  25. * @param {O | null | undefined} second second object
  26. * @returns {T & O | T | O} merged object of first and second object
  27. */
  28. const cachedCleverMerge = (first, second) => {
  29. if (second === undefined) return /** @type {T} */ (first);
  30. if (first === undefined) return /** @type {O} */ (second);
  31. if (typeof second !== "object" || second === null) {
  32. return /** @type {O} */ (second);
  33. }
  34. if (typeof first !== "object" || first === null) {
  35. return /** @type {T} */ (first);
  36. }
  37. let innerCache = mergeCache.get(first);
  38. if (innerCache === undefined) {
  39. innerCache = new WeakMap();
  40. mergeCache.set(first, innerCache);
  41. }
  42. const prevMerge = /** @type {T & O} */ (innerCache.get(second));
  43. if (prevMerge !== undefined) return prevMerge;
  44. const newMerge = _cleverMerge(first, second, true);
  45. innerCache.set(second, newMerge);
  46. return newMerge;
  47. };
  48. /**
  49. * Caches d set property.
  50. * @template T
  51. * @param {Partial<T>} obj object
  52. * @param {string} property property
  53. * @param {string | number | boolean} value assignment value
  54. * @returns {T} new object
  55. */
  56. const cachedSetProperty = (obj, property, value) => {
  57. let mapByProperty = setPropertyCache.get(obj);
  58. if (mapByProperty === undefined) {
  59. mapByProperty = new Map();
  60. setPropertyCache.set(obj, mapByProperty);
  61. }
  62. let mapByValue = mapByProperty.get(property);
  63. if (mapByValue === undefined) {
  64. mapByValue = new Map();
  65. mapByProperty.set(property, mapByValue);
  66. }
  67. let result = mapByValue.get(value);
  68. if (result) return /** @type {T} */ (result);
  69. result = {
  70. ...obj,
  71. [property]: value
  72. };
  73. mapByValue.set(value, result);
  74. return /** @type {T} */ (result);
  75. };
  76. /**
  77. * Defines the by values type used by this module.
  78. * @typedef {Map<string, EXPECTED_ANY>} ByValues
  79. */
  80. /**
  81. * Defines the object parsed property entry type used by this module.
  82. * @template T
  83. * @typedef {object} ObjectParsedPropertyEntry
  84. * @property {T[keyof T] | undefined} base base value
  85. * @property {`by${string}` | undefined} byProperty the name of the selector property
  86. * @property {ByValues | undefined} byValues value depending on selector property, merged with base
  87. */
  88. /** @typedef {(function(...EXPECTED_ANY): object) & { [DYNAMIC_INFO]: [DynamicFunction, object] }} DynamicFunction */
  89. /**
  90. * Defines the parsed object static type used by this module.
  91. * @template {object} T
  92. * @typedef {Map<keyof T, ObjectParsedPropertyEntry<T>>} ParsedObjectStatic
  93. */
  94. /**
  95. * Defines the parsed object dynamic type used by this module.
  96. * @template {object} T
  97. * @typedef {{ byProperty: `by${string}`, fn: DynamicFunction }} ParsedObjectDynamic
  98. */
  99. /**
  100. * Defines the parsed object type used by this module.
  101. * @template {object} T
  102. * @typedef {object} ParsedObject
  103. * @property {ParsedObjectStatic<T>} static static properties (key is property name)
  104. * @property {ParsedObjectDynamic<T> | undefined} dynamic dynamic part
  105. */
  106. /** @type {WeakMap<EXPECTED_OBJECT, ParsedObject<EXPECTED_ANY>>} */
  107. const parseCache = new WeakMap();
  108. /**
  109. * Caches d parse object.
  110. * @template {object} T
  111. * @param {T} obj the object
  112. * @returns {ParsedObject<T>} parsed object
  113. */
  114. const cachedParseObject = (obj) => {
  115. const entry = parseCache.get(/** @type {EXPECTED_OBJECT} */ (obj));
  116. if (entry !== undefined) return entry;
  117. const result = parseObject(obj);
  118. parseCache.set(/** @type {EXPECTED_OBJECT} */ (obj), result);
  119. return result;
  120. };
  121. /** @typedef {{ [p: string]: { [p: string]: EXPECTED_ANY } } | DynamicFunction} ByObject */
  122. /**
  123. * Returns parsed object.
  124. * @template {object} T
  125. * @param {T} obj the object
  126. * @returns {ParsedObject<T>} parsed object
  127. */
  128. const parseObject = (obj) => {
  129. /** @type {ParsedObjectStatic<T>} */
  130. const info = new Map();
  131. /** @type {ParsedObjectDynamic<T> | undefined} */
  132. let dynamicInfo;
  133. /**
  134. * Returns object parsed property entry.
  135. * @param {keyof T} p path
  136. * @returns {Partial<ObjectParsedPropertyEntry<T>>} object parsed property entry
  137. */
  138. const getInfo = (p) => {
  139. const entry = info.get(p);
  140. if (entry !== undefined) return entry;
  141. const newEntry = {
  142. base: undefined,
  143. byProperty: undefined,
  144. byValues: undefined
  145. };
  146. info.set(p, newEntry);
  147. return newEntry;
  148. };
  149. for (const key_ of Object.keys(obj)) {
  150. const key = /** @type {keyof T} */ (key_);
  151. if (typeof key === "string" && key.startsWith("by")) {
  152. const byProperty = key;
  153. const byObj = /** @type {ByObject} */ (obj[byProperty]);
  154. if (typeof byObj === "object") {
  155. for (const byValue of Object.keys(byObj)) {
  156. const obj = byObj[/** @type {keyof (keyof T)} */ (byValue)];
  157. for (const key of Object.keys(obj)) {
  158. const entry = getInfo(/** @type {keyof T} */ (key));
  159. if (entry.byProperty === undefined) {
  160. entry.byProperty = /** @type {`by${string}`} */ (byProperty);
  161. entry.byValues = new Map();
  162. } else if (entry.byProperty !== byProperty) {
  163. throw new Error(
  164. `${/** @type {string} */ (byProperty)} and ${entry.byProperty} for a single property is not supported`
  165. );
  166. }
  167. /** @type {ByValues} */
  168. (entry.byValues).set(byValue, obj[key]);
  169. if (byValue === "default") {
  170. for (const otherByValue of Object.keys(byObj)) {
  171. if (
  172. !(
  173. /** @type {ByValues} */
  174. (entry.byValues).has(otherByValue)
  175. )
  176. ) {
  177. /** @type {ByValues} */
  178. (entry.byValues).set(otherByValue, undefined);
  179. }
  180. }
  181. }
  182. }
  183. }
  184. } else if (typeof byObj === "function") {
  185. if (dynamicInfo === undefined) {
  186. dynamicInfo = {
  187. byProperty: /** @type {`by${string}`} */ (key),
  188. fn: byObj
  189. };
  190. } else {
  191. throw new Error(
  192. `${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
  193. );
  194. }
  195. } else {
  196. const entry = getInfo(key);
  197. entry.base = obj[key];
  198. }
  199. } else {
  200. const entry = getInfo(key);
  201. entry.base = obj[key];
  202. }
  203. }
  204. return {
  205. static: info,
  206. dynamic: dynamicInfo
  207. };
  208. };
  209. /**
  210. * Returns the object.
  211. * @template {object} T
  212. * @param {ParsedObjectStatic<T>} info static properties (key is property name)
  213. * @param {{ byProperty: `by${string}`, fn: DynamicFunction } | undefined} dynamicInfo dynamic part
  214. * @returns {T} the object
  215. */
  216. const serializeObject = (info, dynamicInfo) => {
  217. const obj = /** @type {EXPECTED_ANY} */ ({});
  218. // Setup byProperty structure
  219. for (const entry of info.values()) {
  220. if (entry.byProperty !== undefined) {
  221. const byProperty = entry.byProperty;
  222. const byObj = (obj[byProperty] = obj[byProperty] || {});
  223. for (const byValue of /** @type {ByValues} */ (entry.byValues).keys()) {
  224. byObj[byValue] = byObj[byValue] || {};
  225. }
  226. }
  227. }
  228. for (const [key, entry] of info) {
  229. if (entry.base !== undefined) {
  230. obj[key] = entry.base;
  231. }
  232. // Fill byProperty structure
  233. if (entry.byProperty !== undefined) {
  234. const byProperty = entry.byProperty;
  235. const byObj = (obj[byProperty] = obj[byProperty] || {});
  236. for (const byValue of Object.keys(byObj)) {
  237. const value = getFromByValues(
  238. /** @type {ByValues} */
  239. (entry.byValues),
  240. byValue
  241. );
  242. if (value !== undefined) byObj[byValue][key] = value;
  243. }
  244. }
  245. }
  246. if (dynamicInfo !== undefined) {
  247. obj[dynamicInfo.byProperty] = dynamicInfo.fn;
  248. }
  249. return obj;
  250. };
  251. const VALUE_TYPE_UNDEFINED = 0;
  252. const VALUE_TYPE_ATOM = 1;
  253. const VALUE_TYPE_ARRAY_EXTEND = 2;
  254. const VALUE_TYPE_OBJECT = 3;
  255. const VALUE_TYPE_DELETE = 4;
  256. /**
  257. * Returns value type.
  258. * @template T
  259. * @param {T} value a single value
  260. * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type
  261. */
  262. const getValueType = (value) => {
  263. if (value === undefined) {
  264. return VALUE_TYPE_UNDEFINED;
  265. } else if (value === DELETE) {
  266. return VALUE_TYPE_DELETE;
  267. } else if (Array.isArray(value)) {
  268. if (value.includes("...")) return VALUE_TYPE_ARRAY_EXTEND;
  269. return VALUE_TYPE_ATOM;
  270. } else if (
  271. typeof value === "object" &&
  272. value !== null &&
  273. (!value.constructor || value.constructor === Object)
  274. ) {
  275. return VALUE_TYPE_OBJECT;
  276. }
  277. return VALUE_TYPE_ATOM;
  278. };
  279. /**
  280. * Merges two objects. Objects are deeply clever merged.
  281. * Arrays might reference the old value with "...".
  282. * Non-object values take preference over object values.
  283. * @template T
  284. * @template O
  285. * @param {T} first first object
  286. * @param {O} second second object
  287. * @returns {T & O | T | O} merged object of first and second object
  288. */
  289. const cleverMerge = (first, second) => {
  290. if (second === undefined) return first;
  291. if (first === undefined) return second;
  292. if (typeof second !== "object" || second === null) return second;
  293. if (typeof first !== "object" || first === null) return first;
  294. return /** @type {T & O} */ (_cleverMerge(first, second, false));
  295. };
  296. /**
  297. * Returns merged object of first and second object.
  298. * @template {object} T
  299. * @template {object} O
  300. * Merges two objects. Objects are deeply clever merged.
  301. * @param {T} first first
  302. * @param {O} second second
  303. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  304. * @returns {T & O} merged object of first and second object
  305. */
  306. const _cleverMerge = (first, second, internalCaching = false) => {
  307. const firstObject = internalCaching
  308. ? cachedParseObject(first)
  309. : parseObject(first);
  310. const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject;
  311. // If the first argument has a dynamic part we modify the dynamic part to merge the second argument
  312. if (firstDynamicInfo !== undefined) {
  313. let { byProperty, fn } = firstDynamicInfo;
  314. const fnInfo = fn[DYNAMIC_INFO];
  315. if (fnInfo) {
  316. second =
  317. /** @type {O} */
  318. (
  319. internalCaching
  320. ? cachedCleverMerge(fnInfo[1], second)
  321. : cleverMerge(fnInfo[1], second)
  322. );
  323. fn = fnInfo[0];
  324. }
  325. /** @type {DynamicFunction} */
  326. const newFn = (...args) => {
  327. const fnResult = fn(...args);
  328. return internalCaching
  329. ? cachedCleverMerge(fnResult, second)
  330. : cleverMerge(fnResult, second);
  331. };
  332. newFn[DYNAMIC_INFO] = [fn, second];
  333. return /** @type {T & O} */ (
  334. serializeObject(firstObject.static, { byProperty, fn: newFn })
  335. );
  336. }
  337. // If the first part is static only, we merge the static parts and keep the dynamic part of the second argument
  338. const secondObject = internalCaching
  339. ? cachedParseObject(second)
  340. : parseObject(second);
  341. const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject;
  342. const resultInfo = new Map();
  343. for (const [key, firstEntry] of firstInfo) {
  344. const secondEntry = secondInfo.get(
  345. /** @type {keyof (T | O)} */
  346. (key)
  347. );
  348. const entry =
  349. secondEntry !== undefined
  350. ? mergeEntries(firstEntry, secondEntry, internalCaching)
  351. : firstEntry;
  352. resultInfo.set(key, entry);
  353. }
  354. for (const [key, secondEntry] of secondInfo) {
  355. if (!firstInfo.has(/** @type {keyof (T | O)} */ (key))) {
  356. resultInfo.set(key, secondEntry);
  357. }
  358. }
  359. return /** @type {T & O} */ (serializeObject(resultInfo, secondDynamicInfo));
  360. };
  361. /**
  362. * Merges the provided values into a single result.
  363. * @template T, O
  364. * @param {ObjectParsedPropertyEntry<T>} firstEntry a
  365. * @param {ObjectParsedPropertyEntry<O>} secondEntry b
  366. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  367. * @returns {ObjectParsedPropertyEntry<T> | ObjectParsedPropertyEntry<O> | ObjectParsedPropertyEntry<T & O>} new entry
  368. */
  369. const mergeEntries = (firstEntry, secondEntry, internalCaching) => {
  370. switch (getValueType(secondEntry.base)) {
  371. case VALUE_TYPE_ATOM:
  372. case VALUE_TYPE_DELETE:
  373. // No need to consider firstEntry at all
  374. // second value override everything
  375. // = second.base + second.byProperty
  376. return secondEntry;
  377. case VALUE_TYPE_UNDEFINED:
  378. if (!firstEntry.byProperty) {
  379. // = first.base + second.byProperty
  380. return {
  381. base: firstEntry.base,
  382. byProperty: secondEntry.byProperty,
  383. byValues: secondEntry.byValues
  384. };
  385. } else if (firstEntry.byProperty !== secondEntry.byProperty) {
  386. throw new Error(
  387. `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
  388. );
  389. } else {
  390. // = first.base + (first.byProperty + second.byProperty)
  391. // need to merge first and second byValues
  392. /** @type {Map<string, T & O>} */
  393. const newByValues = new Map(firstEntry.byValues);
  394. for (const [key, value] of /** @type {ByValues} */ (
  395. secondEntry.byValues
  396. )) {
  397. const firstValue = getFromByValues(
  398. /** @type {ByValues} */
  399. (firstEntry.byValues),
  400. key
  401. );
  402. newByValues.set(
  403. key,
  404. mergeSingleValue(firstValue, value, internalCaching)
  405. );
  406. }
  407. return {
  408. base: firstEntry.base,
  409. byProperty: firstEntry.byProperty,
  410. byValues: newByValues
  411. };
  412. }
  413. default: {
  414. if (!firstEntry.byProperty) {
  415. // The simple case
  416. // = (first.base + second.base) + second.byProperty
  417. return {
  418. base:
  419. /** @type {T[keyof T] & O[keyof O]} */
  420. (
  421. mergeSingleValue(
  422. firstEntry.base,
  423. secondEntry.base,
  424. internalCaching
  425. )
  426. ),
  427. byProperty: secondEntry.byProperty,
  428. byValues: secondEntry.byValues
  429. };
  430. }
  431. /** @type {O[keyof O] | T[keyof T] | (T[keyof T] & O[keyof O]) | (T[keyof T] | undefined)[] | (O[keyof O] | undefined)[] | (O[keyof O] | T[keyof T] | undefined)[] | undefined} */
  432. let newBase;
  433. /** @type {Map<string, (T & O) | O[keyof O] | (O[keyof O] | undefined)[] | ((T & O) | undefined)[] | (T & O & O[keyof O]) | ((T & O) | O[keyof O] | undefined)[] | undefined>} */
  434. const intermediateByValues = new Map(firstEntry.byValues);
  435. for (const [key, value] of intermediateByValues) {
  436. intermediateByValues.set(
  437. key,
  438. mergeSingleValue(value, secondEntry.base, internalCaching)
  439. );
  440. }
  441. if (
  442. [.../** @type {ByValues} */ (firstEntry.byValues).values()].every(
  443. (value) => {
  444. const type = getValueType(value);
  445. return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE;
  446. }
  447. )
  448. ) {
  449. // = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
  450. newBase = mergeSingleValue(
  451. firstEntry.base,
  452. secondEntry.base,
  453. internalCaching
  454. );
  455. } else {
  456. // = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
  457. newBase = firstEntry.base;
  458. if (!intermediateByValues.has("default")) {
  459. intermediateByValues.set("default", secondEntry.base);
  460. }
  461. }
  462. if (!secondEntry.byProperty) {
  463. // = first.base + (first.byProperty + second.base)
  464. return {
  465. base: /** @type {T[keyof T] & O[keyof O]} */ (newBase),
  466. byProperty: firstEntry.byProperty,
  467. byValues: intermediateByValues
  468. };
  469. } else if (firstEntry.byProperty !== secondEntry.byProperty) {
  470. throw new Error(
  471. `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
  472. );
  473. }
  474. /** @type {Map<string, (T & O) | O[keyof O] | (O[keyof O] | undefined)[] | (T & O & O[keyof O]) | ((T & O) | undefined)[] | ((T & O) | O[keyof O] | undefined)[] | undefined>} */
  475. const newByValues = new Map(intermediateByValues);
  476. for (const [key, value] of /** @type {ByValues} */ (
  477. secondEntry.byValues
  478. )) {
  479. const firstValue = getFromByValues(intermediateByValues, key);
  480. newByValues.set(
  481. key,
  482. mergeSingleValue(firstValue, value, internalCaching)
  483. );
  484. }
  485. return {
  486. base: /** @type {T[keyof T] & O[keyof O]} */ (newBase),
  487. byProperty: firstEntry.byProperty,
  488. byValues: newByValues
  489. };
  490. }
  491. }
  492. };
  493. /**
  494. * Gets from by values.
  495. * @template V
  496. * @param {ByValues} byValues all values
  497. * @param {string} key value of the selector
  498. * @returns {V | undefined} value
  499. */
  500. const getFromByValues = (byValues, key) => {
  501. if (key !== "default" && byValues.has(key)) {
  502. return byValues.get(key);
  503. }
  504. return byValues.get("default");
  505. };
  506. /**
  507. * Merges single value.
  508. * @template A
  509. * @template B
  510. * @param {A | A[]} a value
  511. * @param {B | B[]} b value
  512. * @param {boolean} internalCaching should parsing of objects and nested merges be cached
  513. * @returns {A & B | (A | B)[] | A | A[] | B | B[]} value
  514. */
  515. const mergeSingleValue = (a, b, internalCaching) => {
  516. const bType = getValueType(b);
  517. const aType = getValueType(a);
  518. switch (bType) {
  519. case VALUE_TYPE_DELETE:
  520. case VALUE_TYPE_ATOM:
  521. return b;
  522. case VALUE_TYPE_OBJECT: {
  523. return aType !== VALUE_TYPE_OBJECT
  524. ? b
  525. : internalCaching
  526. ? cachedCleverMerge(a, b)
  527. : cleverMerge(a, b);
  528. }
  529. case VALUE_TYPE_UNDEFINED:
  530. return a;
  531. case VALUE_TYPE_ARRAY_EXTEND:
  532. switch (
  533. aType !== VALUE_TYPE_ATOM
  534. ? aType
  535. : Array.isArray(a)
  536. ? VALUE_TYPE_ARRAY_EXTEND
  537. : VALUE_TYPE_OBJECT
  538. ) {
  539. case VALUE_TYPE_UNDEFINED:
  540. return b;
  541. case VALUE_TYPE_DELETE:
  542. return /** @type {B[]} */ (b).filter((item) => item !== "...");
  543. case VALUE_TYPE_ARRAY_EXTEND: {
  544. /** @type {(A | B)[]} */
  545. const newArray = [];
  546. for (const item of /** @type {B[]} */ (b)) {
  547. if (item === "...") {
  548. for (const item of /** @type {A[]} */ (a)) {
  549. newArray.push(item);
  550. }
  551. } else {
  552. newArray.push(item);
  553. }
  554. }
  555. return newArray;
  556. }
  557. case VALUE_TYPE_OBJECT:
  558. return /** @type {(A | B)[]} */ (b).map((item) =>
  559. item === "..." ? /** @type {A} */ (a) : item
  560. );
  561. default:
  562. throw new Error("Not implemented");
  563. }
  564. default:
  565. throw new Error("Not implemented");
  566. }
  567. };
  568. /**
  569. * Removes operations.
  570. * @template {object} T
  571. * @param {T} obj the object
  572. * @param {(keyof T)[]=} keysToKeepOriginalValue keys to keep original value
  573. * @returns {T} the object without operations like "..." or DELETE
  574. */
  575. const removeOperations = (obj, keysToKeepOriginalValue = []) => {
  576. const newObj = /** @type {T} */ ({});
  577. for (const _key of Object.keys(obj)) {
  578. const key = /** @type {keyof T} */ (_key);
  579. const value = obj[key];
  580. const type = getValueType(value);
  581. if (type === VALUE_TYPE_OBJECT && keysToKeepOriginalValue.includes(key)) {
  582. newObj[key] = value;
  583. continue;
  584. }
  585. switch (type) {
  586. case VALUE_TYPE_UNDEFINED:
  587. case VALUE_TYPE_DELETE:
  588. break;
  589. case VALUE_TYPE_OBJECT:
  590. newObj[key] =
  591. /** @type {T[keyof T]} */
  592. (
  593. removeOperations(
  594. /** @type {T} */
  595. (value),
  596. keysToKeepOriginalValue
  597. )
  598. );
  599. break;
  600. case VALUE_TYPE_ARRAY_EXTEND:
  601. newObj[key] =
  602. /** @type {T[keyof T]} */
  603. (
  604. /** @type {EXPECTED_ANY[]} */
  605. (value).filter((i) => i !== "...")
  606. );
  607. break;
  608. default:
  609. newObj[key] = value;
  610. break;
  611. }
  612. }
  613. return newObj;
  614. };
  615. /**
  616. * Resolves by property.
  617. * @template T
  618. * @template {keyof T} P
  619. * @template V
  620. * @param {T} obj the object
  621. * @param {P} byProperty the by description
  622. * @param {...V} values values
  623. * @returns {Omit<T, P>} object with merged byProperty
  624. */
  625. const resolveByProperty = (obj, byProperty, ...values) => {
  626. if (typeof obj !== "object" || obj === null || !(byProperty in obj)) {
  627. return obj;
  628. }
  629. const { [byProperty]: _byValue, ..._remaining } = obj;
  630. const remaining = /** @type {T} */ (_remaining);
  631. const byValue =
  632. /** @type {Record<string, T> | ((...args: V[]) => T)} */
  633. (_byValue);
  634. if (typeof byValue === "object") {
  635. const key = /** @type {string} */ (values[0]);
  636. if (key in byValue) {
  637. return cachedCleverMerge(remaining, byValue[key]);
  638. } else if ("default" in byValue) {
  639. return cachedCleverMerge(remaining, byValue.default);
  640. }
  641. return remaining;
  642. } else if (typeof byValue === "function") {
  643. // eslint-disable-next-line prefer-spread
  644. const result = byValue.apply(null, values);
  645. return cachedCleverMerge(
  646. remaining,
  647. resolveByProperty(result, byProperty, ...values)
  648. );
  649. }
  650. return obj;
  651. };
  652. module.exports.DELETE = DELETE;
  653. module.exports.cachedCleverMerge = cachedCleverMerge;
  654. module.exports.cachedSetProperty = cachedSetProperty;
  655. module.exports.cleverMerge = cleverMerge;
  656. module.exports.removeOperations = removeOperations;
  657. module.exports.resolveByProperty = resolveByProperty;