cleverMerge.js 20 KB

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