timm.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. /* eslint-disable @typescript-eslint/ban-types */
  2. /*!
  3. * Timm
  4. *
  5. * Immutability helpers with fast reads and acceptable writes.
  6. *
  7. * @copyright Guillermo Grau Panea 2016
  8. * @license MIT
  9. */
  10. const INVALID_ARGS = 'INVALID_ARGS';
  11. const IS_DEV = (process as any).env.NODE_ENV !== 'production';
  12. type Key = string | number;
  13. // ===============================================
  14. // ### Helpers
  15. // ===============================================
  16. function throwStr(msg: string): never {
  17. throw new Error(msg);
  18. }
  19. function getKeysAndSymbols(obj: object): string[] {
  20. const keys = Object.keys(obj);
  21. if (Object.getOwnPropertySymbols) {
  22. // @ts-ignore
  23. return keys.concat(Object.getOwnPropertySymbols(obj));
  24. }
  25. return keys;
  26. }
  27. const hasOwnProperty = {}.hasOwnProperty;
  28. export function clone<T extends object>(obj0: T): T {
  29. // As array
  30. if (Array.isArray(obj0)) return obj0.slice() as T;
  31. // As object
  32. const obj: any = obj0;
  33. const keys = getKeysAndSymbols(obj);
  34. const out: any = {};
  35. for (let i = 0; i < keys.length; i++) {
  36. const key = keys[i];
  37. out[key] = obj[key];
  38. }
  39. // @ts-ignore (see type tests)
  40. return out;
  41. }
  42. // Custom guard
  43. function isObject(o: unknown): any {
  44. return o != null && typeof o === 'object';
  45. }
  46. // _deepFreeze = (obj) ->
  47. // Object.freeze obj
  48. // for key in Object.getOwnPropertyNames obj
  49. // val = obj[key]
  50. // if isObject(val) and not Object.isFrozen val
  51. // _deepFreeze val
  52. // obj
  53. // ===============================================
  54. // -- ### Arrays
  55. // ===============================================
  56. // -- #### addLast()
  57. // -- Returns a new array with an appended item or items.
  58. // --
  59. // -- Usage: `addLast(array, val)`
  60. // --
  61. // -- ```js
  62. // -- arr = ['a', 'b']
  63. // -- arr2 = addLast(arr, 'c')
  64. // -- // ['a', 'b', 'c']
  65. // -- arr2 === arr
  66. // -- // false
  67. // -- arr3 = addLast(arr, ['c', 'd'])
  68. // -- // ['a', 'b', 'c', 'd']
  69. // -- ```
  70. // `array.concat(val)` also handles the scalar case,
  71. // but is apparently very slow
  72. export function addLast<T>(array: T[], val: T[] | T): T[] {
  73. if (Array.isArray(val)) return array.concat(val);
  74. return array.concat([val]);
  75. }
  76. // -- #### addFirst()
  77. // -- Returns a new array with a prepended item or items.
  78. // --
  79. // -- Usage: `addFirst(array, val)`
  80. // --
  81. // -- ```js
  82. // -- arr = ['a', 'b']
  83. // -- arr2 = addFirst(arr, 'c')
  84. // -- // ['c', 'a', 'b']
  85. // -- arr2 === arr
  86. // -- // false
  87. // -- arr3 = addFirst(arr, ['c', 'd'])
  88. // -- // ['c', 'd', 'a', 'b']
  89. // -- ```
  90. export function addFirst<T>(array: T[], val: T[] | T): T[] {
  91. if (Array.isArray(val)) return val.concat(array);
  92. return [val].concat(array);
  93. }
  94. // -- #### removeLast()
  95. // -- Returns a new array removing the last item.
  96. // --
  97. // -- Usage: `removeLast(array)`
  98. // --
  99. // -- ```js
  100. // -- arr = ['a', 'b']
  101. // -- arr2 = removeLast(arr)
  102. // -- // ['a']
  103. // -- arr2 === arr
  104. // -- // false
  105. // --
  106. // -- // The same array is returned if there are no changes:
  107. // -- arr3 = []
  108. // -- removeLast(arr3) === arr3
  109. // -- // true
  110. // -- ```
  111. export function removeLast<T>(array: T[]): T[] {
  112. if (!array.length) return array;
  113. return array.slice(0, array.length - 1);
  114. }
  115. // -- #### removeFirst()
  116. // -- Returns a new array removing the first item.
  117. // --
  118. // -- Usage: `removeFirst(array)`
  119. // --
  120. // -- ```js
  121. // -- arr = ['a', 'b']
  122. // -- arr2 = removeFirst(arr)
  123. // -- // ['b']
  124. // -- arr2 === arr
  125. // -- // false
  126. // --
  127. // -- // The same array is returned if there are no changes:
  128. // -- arr3 = []
  129. // -- removeFirst(arr3) === arr3
  130. // -- // true
  131. // -- ```
  132. export function removeFirst<T>(array: T[]): T[] {
  133. if (!array.length) return array;
  134. return array.slice(1);
  135. }
  136. // -- #### insert()
  137. // -- Returns a new array obtained by inserting an item or items
  138. // -- at a specified index.
  139. // --
  140. // -- Usage: `insert(array, idx, val)`
  141. // --
  142. // -- ```js
  143. // -- arr = ['a', 'b', 'c']
  144. // -- arr2 = insert(arr, 1, 'd')
  145. // -- // ['a', 'd', 'b', 'c']
  146. // -- arr2 === arr
  147. // -- // false
  148. // -- insert(arr, 1, ['d', 'e'])
  149. // -- // ['a', 'd', 'e', 'b', 'c']
  150. // -- ```
  151. export function insert<T>(array: T[], idx: number, val: T[] | T): T[] {
  152. return array
  153. .slice(0, idx)
  154. .concat(Array.isArray(val) ? val : [val])
  155. .concat(array.slice(idx));
  156. }
  157. // -- #### removeAt()
  158. // -- Returns a new array obtained by removing an item at
  159. // -- a specified index.
  160. // --
  161. // -- Usage: `removeAt(array, idx)`
  162. // --
  163. // -- ```js
  164. // -- arr = ['a', 'b', 'c']
  165. // -- arr2 = removeAt(arr, 1)
  166. // -- // ['a', 'c']
  167. // -- arr2 === arr
  168. // -- // false
  169. // --
  170. // -- // The same array is returned if there are no changes:
  171. // -- removeAt(arr, 4) === arr
  172. // -- // true
  173. // -- ```
  174. export function removeAt<T>(array: T[], idx: number): T[] {
  175. if (idx >= array.length || idx < 0) return array;
  176. return array.slice(0, idx).concat(array.slice(idx + 1));
  177. }
  178. // -- #### replaceAt()
  179. // -- Returns a new array obtained by replacing an item at
  180. // -- a specified index. If the provided item is the same as
  181. // -- (*referentially equal to*) the previous item at that position,
  182. // -- the original array is returned.
  183. // --
  184. // -- Usage: `replaceAt(array, idx, newItem)`
  185. // --
  186. // -- ```js
  187. // -- arr = ['a', 'b', 'c']
  188. // -- arr2 = replaceAt(arr, 1, 'd')
  189. // -- // ['a', 'd', 'c']
  190. // -- arr2 === arr
  191. // -- // false
  192. // --
  193. // -- // The same object is returned if there are no changes:
  194. // -- replaceAt(arr, 1, 'b') === arr
  195. // -- // true
  196. // -- ```
  197. export function replaceAt<T>(array: T[], idx: number, newItem: T): T[] {
  198. if (array[idx] === newItem) return array;
  199. const len = array.length;
  200. const result = Array(len);
  201. for (let i = 0; i < len; i++) {
  202. result[i] = array[i];
  203. }
  204. result[idx] = newItem;
  205. return result;
  206. }
  207. // ===============================================
  208. // -- ### Collections (objects and arrays)
  209. // ===============================================
  210. // -- #### getIn()
  211. // -- Returns a value from an object at a given path. Works with
  212. // -- nested arrays and objects. If the path does not exist, it returns
  213. // -- `undefined`.
  214. // --
  215. // -- Usage: `getIn(obj, path)`
  216. // --
  217. // -- ```js
  218. // -- obj = { a: 1, b: 2, d: { d1: 3, d2: 4 }, e: ['a', 'b', 'c'] }
  219. // -- getIn(obj, ['d', 'd1'])
  220. // -- // 3
  221. // -- getIn(obj, ['e', 1])
  222. // -- // 'b'
  223. // -- ```
  224. export function getIn(obj: undefined, path: Key[]): undefined;
  225. export function getIn(obj: null, path: Key[]): null;
  226. export function getIn(obj: object, path: Key[]): unknown;
  227. export function getIn(obj: any, path: Key[]): unknown {
  228. if (!Array.isArray(path)) {
  229. throwStr(
  230. IS_DEV
  231. ? 'A path array should be provided when calling getIn()'
  232. : INVALID_ARGS
  233. );
  234. }
  235. if (obj == null) return undefined;
  236. let ptr: any = obj;
  237. for (let i = 0; i < path.length; i++) {
  238. const key = path[i];
  239. ptr = ptr != null ? ptr[key] : undefined;
  240. if (ptr === undefined) return ptr;
  241. }
  242. return ptr;
  243. }
  244. // -- #### set()
  245. // -- Returns a new object with a modified attribute.
  246. // -- If the provided value is the same as (*referentially equal to*)
  247. // -- the previous value, the original object is returned.
  248. // --
  249. // -- Usage: `set(obj, key, val)`
  250. // --
  251. // -- ```js
  252. // -- obj = { a: 1, b: 2, c: 3 }
  253. // -- obj2 = set(obj, 'b', 5)
  254. // -- // { a: 1, b: 5, c: 3 }
  255. // -- obj2 === obj
  256. // -- // false
  257. // --
  258. // -- // The same object is returned if there are no changes:
  259. // -- set(obj, 'b', 2) === obj
  260. // -- // true
  261. // -- ```
  262. // When called with an undefined/null `obj`, `set()` returns either
  263. // a single-element array, or a single-key object
  264. export function set<K extends string, V>(
  265. obj: undefined | null,
  266. key: K,
  267. val: V
  268. ): { [P in K]: V };
  269. export function set<V>(obj: undefined | null, key: number, val: V): [V];
  270. // Normal operation with an object/array
  271. export function set<T extends object, K extends string, V>(
  272. obj: T,
  273. key: K,
  274. val: V
  275. ): Omit<T, keyof { [P in K]: any }> & { [P in K]: V };
  276. export function set<V>(obj: V[], key: number, val: V): V[];
  277. // Implementation
  278. export function set(obj0: any, key: Key, val: any): any {
  279. let obj = obj0;
  280. if (obj == null) obj = typeof key === 'number' ? [] : {};
  281. if (obj[key] === val) return obj;
  282. const obj2 = clone(obj);
  283. obj2[key] = val;
  284. return obj2;
  285. }
  286. // -- #### setIn()
  287. // -- Returns a new object with a modified **nested** attribute.
  288. // --
  289. // -- Notes:
  290. // --
  291. // -- * If the provided value is the same as (*referentially equal to*)
  292. // -- the previous value, the original object is returned.
  293. // -- * If the path does not exist, it will be created before setting
  294. // -- the new value.
  295. // --
  296. // -- Usage: `setIn(obj, path, val)`
  297. // --
  298. // -- ```js
  299. // -- obj = { a: 1, b: 2, d: { d1: 3, d2: 4 }, e: { e1: 'foo', e2: 'bar' } }
  300. // -- obj2 = setIn(obj, ['d', 'd1'], 4)
  301. // -- // { a: 1, b: 2, d: { d1: 4, d2: 4 }, e: { e1: 'foo', e2: 'bar' } }
  302. // -- obj2 === obj
  303. // -- // false
  304. // -- obj2.d === obj.d
  305. // -- // false
  306. // -- obj2.e === obj.e
  307. // -- // true
  308. // --
  309. // -- // The same object is returned if there are no changes:
  310. // -- obj3 = setIn(obj, ['d', 'd1'], 3)
  311. // -- // { a: 1, b: 2, d: { d1: 3, d2: 4 }, e: { e1: 'foo', e2: 'bar' } }
  312. // -- obj3 === obj
  313. // -- // true
  314. // -- obj3.d === obj.d
  315. // -- // true
  316. // -- obj3.e === obj.e
  317. // -- // true
  318. // --
  319. // -- // ... unknown paths create intermediate keys. Numeric segments are treated as array indices:
  320. // -- setIn({ a: 3 }, ['unknown', 0, 'path'], 4)
  321. // -- // { a: 3, unknown: [{ path: 4 }] }
  322. // -- ```
  323. export function setIn(
  324. obj: object | null | undefined,
  325. path: Key[],
  326. val: any
  327. ): unknown {
  328. if (!path.length) return val;
  329. return doSetIn(obj, path, val, 0);
  330. }
  331. function doSetIn(
  332. obj: object | null | undefined,
  333. path: Key[],
  334. val: any,
  335. idx: number
  336. ): unknown {
  337. let newValue;
  338. const key: any = path[idx];
  339. if (idx === path.length - 1) {
  340. newValue = val;
  341. } else {
  342. const nestedObj =
  343. isObject(obj) && isObject((obj as any)[key])
  344. ? (obj as any)[key]
  345. : typeof path[idx + 1] === 'number'
  346. ? []
  347. : {};
  348. newValue = doSetIn(nestedObj, path, val, idx + 1);
  349. }
  350. return set(obj as any, key, newValue);
  351. }
  352. // -- #### update()
  353. // -- Returns a new object with a modified attribute,
  354. // -- calculated via a user-provided callback based on the current value.
  355. // -- If the calculated value is the same as (*referentially equal to*)
  356. // -- the previous value, the original object is returned.
  357. // --
  358. // -- Usage: `update(obj, key, fnUpdate)`
  359. // --
  360. // -- ```js
  361. // -- obj = { a: 1, b: 2, c: 3 }
  362. // -- obj2 = update(obj, 'b', (val) => val + 1)
  363. // -- // { a: 1, b: 3, c: 3 }
  364. // -- obj2 === obj
  365. // -- // false
  366. // --
  367. // -- // The same object is returned if there are no changes:
  368. // -- update(obj, 'b', (val) => val) === obj
  369. // -- // true
  370. // -- ```
  371. export function update(
  372. obj: object | null | undefined,
  373. key: Key,
  374. fnUpdate: (prevValue: any) => any
  375. ): unknown {
  376. const prevVal = obj == null ? undefined : (obj as any)[key];
  377. const nextVal = fnUpdate(prevVal);
  378. return set(obj as any, key as any, nextVal);
  379. }
  380. // -- #### updateIn()
  381. // -- Returns a new object with a modified **nested** attribute,
  382. // -- calculated via a user-provided callback based on the current value.
  383. // -- If the calculated value is the same as (*referentially equal to*)
  384. // -- the previous value, the original object is returned.
  385. // --
  386. // -- Usage: `updateIn<T: ArrayOrObject>(obj: T, path: Array<Key>,
  387. // -- fnUpdate: (prevValue: any) => any): T`
  388. // --
  389. // -- ```js
  390. // -- obj = { a: 1, d: { d1: 3, d2: 4 } }
  391. // -- obj2 = updateIn(obj, ['d', 'd1'], (val) => val + 1)
  392. // -- // { a: 1, d: { d1: 4, d2: 4 } }
  393. // -- obj2 === obj
  394. // -- // false
  395. // --
  396. // -- // The same object is returned if there are no changes:
  397. // -- obj3 = updateIn(obj, ['d', 'd1'], (val) => val)
  398. // -- // { a: 1, d: { d1: 3, d2: 4 } }
  399. // -- obj3 === obj
  400. // -- // true
  401. // -- ```
  402. export function updateIn(
  403. obj: object | null | undefined,
  404. path: Key[],
  405. fnUpdate: (prevValue: any) => any
  406. ): unknown {
  407. const prevVal = getIn(obj as any, path);
  408. const nextVal = fnUpdate(prevVal);
  409. return setIn(obj, path, nextVal);
  410. }
  411. // -- #### merge()
  412. // -- Returns a new object built as follows: the overlapping keys from the
  413. // -- second one overwrite the corresponding entries from the first one.
  414. // -- Similar to `Object.assign()`, but immutable.
  415. // --
  416. // -- Usage:
  417. // --
  418. // -- * `merge(obj1, obj2)`
  419. // -- * `merge(obj1, ...objects)`
  420. // --
  421. // -- The unmodified `obj1` is returned if `obj2` does not *provide something
  422. // -- new to* `obj1`, i.e. if either of the following
  423. // -- conditions are true:
  424. // --
  425. // -- * `obj2` is `null` or `undefined`
  426. // -- * `obj2` is an object, but it is empty
  427. // -- * All attributes of `obj2` are `undefined`
  428. // -- * All attributes of `obj2` are referentially equal to the
  429. // -- corresponding attributes of `obj1`
  430. // --
  431. // -- Note that `undefined` attributes in `obj2` do not modify the
  432. // -- corresponding attributes in `obj1`.
  433. // --
  434. // -- ```js
  435. // -- obj1 = { a: 1, b: 2, c: 3 }
  436. // -- obj2 = { c: 4, d: 5 }
  437. // -- obj3 = merge(obj1, obj2)
  438. // -- // { a: 1, b: 2, c: 4, d: 5 }
  439. // -- obj3 === obj1
  440. // -- // false
  441. // --
  442. // -- // The same object is returned if there are no changes:
  443. // -- merge(obj1, { c: 3 }) === obj1
  444. // -- // true
  445. // -- ```
  446. // Signatures:
  447. // - 1 arg
  448. export function merge<T extends object>(a: T): T;
  449. // - 2 args
  450. export function merge<T extends object>(a: T, b: undefined | null): T;
  451. export function merge<T extends object, U extends object>(
  452. a: T,
  453. b: U
  454. ): Omit<T, keyof U> & U;
  455. // - 3 args
  456. export function merge<T extends object, V extends object>(
  457. a: T,
  458. b: undefined | null,
  459. c: V
  460. ): Omit<T, keyof V> & V;
  461. export function merge<T extends object, U extends object>(
  462. a: T,
  463. b: U,
  464. c: undefined | null
  465. ): Omit<T, keyof U> & U;
  466. export function merge<T extends object>(
  467. a: T,
  468. b: undefined | null,
  469. c: undefined | null
  470. ): T;
  471. export function merge<T extends object, U extends object, V extends object>(
  472. a: T,
  473. b: U,
  474. c: V
  475. ): Omit<Omit<T, keyof U> & U, keyof V> & V;
  476. // - X args
  477. export function merge(a: object, ...rest: Array<object | null>): object;
  478. // Implementation
  479. export function merge(
  480. a: object,
  481. b?: object | null,
  482. c?: object | null,
  483. d?: object | null,
  484. e?: object | null,
  485. f?: object | null,
  486. ...rest: Array<object | null>
  487. ): object {
  488. return rest.length
  489. ? doMerge.call(null, false, false, a, b, c, d, e, f, ...rest)
  490. : doMerge(false, false, a, b, c, d, e, f);
  491. }
  492. // -- #### mergeDeep()
  493. // -- Returns a new object built as follows: the overlapping keys from the
  494. // -- second one overwrite the corresponding entries from the first one.
  495. // -- If both the first and second entries are objects they are merged recursively.
  496. // -- Similar to `Object.assign()`, but immutable, and deeply merging.
  497. // --
  498. // -- Usage:
  499. // --
  500. // -- * `mergeDeep(obj1, obj2)`
  501. // -- * `mergeDeep(obj1, ...objects)`
  502. // --
  503. // -- The unmodified `obj1` is returned if `obj2` does not *provide something
  504. // -- new to* `obj1`, i.e. if either of the following
  505. // -- conditions are true:
  506. // --
  507. // -- * `obj2` is `null` or `undefined`
  508. // -- * `obj2` is an object, but it is empty
  509. // -- * All attributes of `obj2` are `undefined`
  510. // -- * All attributes of `obj2` are referentially equal to the
  511. // -- corresponding attributes of `obj1`
  512. // --
  513. // -- Note that `undefined` attributes in `obj2` do not modify the
  514. // -- corresponding attributes in `obj1`.
  515. // --
  516. // -- ```js
  517. // -- obj1 = { a: 1, b: 2, c: { a: 1 } }
  518. // -- obj2 = { b: 3, c: { b: 2 } }
  519. // -- obj3 = mergeDeep(obj1, obj2)
  520. // -- // { a: 1, b: 3, c: { a: 1, b: 2 } }
  521. // -- obj3 === obj1
  522. // -- // false
  523. // --
  524. // -- // The same object is returned if there are no changes:
  525. // -- mergeDeep(obj1, { c: { a: 1 } }) === obj1
  526. // -- // true
  527. // -- ```
  528. export function mergeDeep(
  529. a: object,
  530. b?: object | null,
  531. c?: object | null,
  532. d?: object | null,
  533. e?: object | null,
  534. f?: object | null,
  535. ...rest: Array<object | null>
  536. ): object {
  537. return rest.length
  538. ? doMerge.call(null, false, true, a, b, c, d, e, f, ...rest)
  539. : doMerge(false, true, a, b, c, d, e, f);
  540. }
  541. // -- #### mergeIn()
  542. // -- Similar to `merge()`, but merging the value at a given nested path.
  543. // --
  544. // -- Usage examples:
  545. // --
  546. // -- * `mergeIn(obj1, path, obj2)`
  547. // -- * `mergeIn(obj1, path, ...objects)`
  548. // --
  549. // -- ```js
  550. // -- obj1 = { a: 1, d: { b: { d1: 3, d2: 4 } } }
  551. // -- obj2 = { d3: 5 }
  552. // -- obj3 = mergeIn(obj1, ['d', 'b'], obj2)
  553. // -- // { a: 1, d: { b: { d1: 3, d2: 4, d3: 5 } } }
  554. // -- obj3 === obj1
  555. // -- // false
  556. // --
  557. // -- // The same object is returned if there are no changes:
  558. // -- mergeIn(obj1, ['d', 'b'], { d2: 4 }) === obj1
  559. // -- // true
  560. // -- ```
  561. export function mergeIn(
  562. a: any,
  563. path: Key[],
  564. b?: object | null,
  565. c?: object | null,
  566. d?: object | null,
  567. e?: object | null,
  568. f?: object | null,
  569. ...rest: Array<object | null>
  570. ): unknown {
  571. let prevVal: any = getIn(a, path);
  572. if (prevVal == null) prevVal = {};
  573. let nextVal;
  574. if (rest.length) {
  575. nextVal = doMerge.call(null, false, false, prevVal, b, c, d, e, f, ...rest);
  576. } else {
  577. nextVal = doMerge(false, false, prevVal, b, c, d, e, f);
  578. }
  579. return setIn(a, path, nextVal);
  580. }
  581. // -- #### omit()
  582. // -- Returns an object excluding one or several attributes.
  583. // --
  584. // -- Usage: `omit(obj, attrs)`
  585. //
  586. // -- ```js
  587. // -- obj = { a: 1, b: 2, c: 3, d: 4 }
  588. // -- omit(obj, 'a')
  589. // -- // { b: 2, c: 3, d: 4 }
  590. // -- omit(obj, ['b', 'c'])
  591. // -- // { a: 1, d: 4 }
  592. // --
  593. // -- // The same object is returned if there are no changes:
  594. // -- omit(obj, 'z') === obj1
  595. // -- // true
  596. // -- ```
  597. export function omit<T extends object, K extends string>(
  598. obj: T,
  599. attrs: K | K[]
  600. ): Omit<T, keyof { [P in K]: any }> {
  601. const omitList = Array.isArray(attrs) ? attrs : [attrs];
  602. let fDoSomething = false;
  603. for (let i = 0; i < omitList.length; i++) {
  604. if (hasOwnProperty.call(obj, omitList[i])) {
  605. fDoSomething = true;
  606. break;
  607. }
  608. }
  609. if (!fDoSomething) return obj;
  610. const out: any = {};
  611. const keys = getKeysAndSymbols(obj);
  612. for (let i = 0; i < keys.length; i++) {
  613. const key: any = keys[i];
  614. if (omitList.indexOf(key) >= 0) continue;
  615. out[key] = (obj as any)[key];
  616. }
  617. return out;
  618. }
  619. // -- #### addDefaults()
  620. // -- Returns a new object built as follows: `undefined` keys in the first one
  621. // -- are filled in with the corresponding values from the second one
  622. // -- (even if they are `null`).
  623. // --
  624. // -- Usage:
  625. // --
  626. // -- * `addDefaults(obj, defaults)`
  627. // -- * `addDefaults(obj, ...defaultObjects)`
  628. // --
  629. // -- ```js
  630. // -- obj1 = { a: 1, b: 2, c: 3 }
  631. // -- obj2 = { c: 4, d: 5, e: null }
  632. // -- obj3 = addDefaults(obj1, obj2)
  633. // -- // { a: 1, b: 2, c: 3, d: 5, e: null }
  634. // -- obj3 === obj1
  635. // -- // false
  636. // --
  637. // -- // The same object is returned if there are no changes:
  638. // -- addDefaults(obj1, { c: 4 }) === obj1
  639. // -- // true
  640. // -- ```
  641. // Signatures:
  642. // - 2 args
  643. export function addDefaults<T extends object, U extends object>(
  644. a: T,
  645. b: U
  646. ): Omit<U, keyof T> & T;
  647. // - X args
  648. export function addDefaults(
  649. a: object,
  650. b: object,
  651. ...rest: Array<object | null>
  652. ): object;
  653. // Implementation and catch-all
  654. export function addDefaults(
  655. a: object,
  656. b: object,
  657. c?: object | null,
  658. d?: object | null,
  659. e?: object | null,
  660. f?: object | null,
  661. ...rest: Array<object | null>
  662. ): object {
  663. return rest.length
  664. ? doMerge.call(null, true, false, a, b, c, d, e, f, ...rest)
  665. : doMerge(true, false, a, b, c, d, e, f);
  666. }
  667. function doMerge(
  668. fAddDefaults: boolean,
  669. fDeep: boolean,
  670. first: object,
  671. ...rest: Array<object | null | undefined>
  672. ) {
  673. let out: any = first;
  674. if (!(out != null)) {
  675. throwStr(
  676. IS_DEV
  677. ? 'At least one object should be provided to merge()'
  678. : INVALID_ARGS
  679. );
  680. }
  681. let fChanged = false;
  682. for (let idx = 0; idx < rest.length; idx++) {
  683. const obj = rest[idx];
  684. if (obj == null) continue;
  685. const keys = getKeysAndSymbols(obj);
  686. if (!keys.length) continue;
  687. for (let j = 0; j <= keys.length; j++) {
  688. const key = keys[j];
  689. if (fAddDefaults && out[key] !== undefined) continue;
  690. let nextVal = (obj as any)[key];
  691. if (fDeep && isObject(out[key]) && isObject(nextVal)) {
  692. nextVal = doMerge(fAddDefaults, fDeep, out[key], nextVal);
  693. }
  694. if (nextVal === undefined || nextVal === out[key]) continue;
  695. if (!fChanged) {
  696. fChanged = true;
  697. out = clone(out);
  698. }
  699. out[key] = nextVal;
  700. }
  701. }
  702. return out;
  703. }
  704. // ===============================================
  705. // ### Public API
  706. // ===============================================
  707. const timm = {
  708. clone,
  709. addLast,
  710. addFirst,
  711. removeLast,
  712. removeFirst,
  713. insert,
  714. removeAt,
  715. replaceAt,
  716. getIn,
  717. set,
  718. setIn,
  719. update,
  720. updateIn,
  721. merge,
  722. mergeDeep,
  723. mergeIn,
  724. omit,
  725. addDefaults,
  726. };
  727. export default timm;