deepie-merge 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
  > Yay, another deep merge
5
5
 
6
6
  ## Usage
7
- ```console
8
- npm i deepie-merge
7
+
8
+ ```sh
9
+ pnpm add deepie-merge
9
10
  ```
10
11
 
11
12
  ```js
@@ -31,6 +32,7 @@ Deep-merge `b` into `a`.
31
32
  - `b` *any*: Source value
32
33
  - `options` *object*:
33
34
  - `arrayExtend` *boolean* or *string[]*: Whether to extend arrays instead of replacing them. When passed a string array, it will only extend the object keys provided in that array.
34
- - `maxRecursions` *number*: Amount of nesting levels to recurse into. Default: `20`
35
+ - `maxRecursion` *number*: Amount of nesting levels to recurse into. Default: `20`
36
+ - `clone` *boolean* or *function*: Return a new value instead of mutating `a`. A function is used to clone `a`, otherwise `structuredClone`. Default: `false`
35
37
 
36
38
  © [silverwind](https://github.com/silverwind), distributed under BSD licence
package/dist/index.d.ts CHANGED
@@ -1,23 +1,18 @@
1
1
  //#region index.d.ts
2
2
  type ArrayExtend = boolean | Array<string>;
3
3
  type DeepieMergeOpts<T = any> = {
4
- /** Either a boolean or a array of property keys to allow extension. Default: false */arrayExtend?: ArrayExtend; /** Maximum recursions to perform. Default: 10. */
5
- maxRecursion?: number; /** Return a new value instead of mutating the first argument. Default: false */
4
+ /** Either a boolean or an array of property keys to allow extension. Default: false */
5
+ arrayExtend?: ArrayExtend;
6
+ /** Maximum recursions to perform. Default: 20. */
7
+ maxRecursion?: number;
8
+ /** Return a new value instead of mutating the first argument. Default: false */
6
9
  clone?: boolean | ((value: NoInfer<T>) => NoInfer<T>);
7
10
  };
8
11
  type DeepMergeable = {
9
12
  [key: string]: any;
10
13
  } | Array<any>;
11
- /** Same top-level shape as `T` (array vs object) with the same keys, but with
12
- * values widened to `unknown`. Catches array/object mismatches and top-level
13
- * excess keys on fresh literals, while accepting wider override types
14
- * (e.g. `Partial<UserConfig>` against a `satisfies`-narrowed literal). */
15
- type DeepMergeSource<T> = T extends ReadonlyArray<any> ? ReadonlyArray<unknown> : T extends object ? { [K in keyof T]?: unknown } : never;
14
+ /** Top-level shape and keys of `T` with `unknown` values: rejects excess keys, accepts wider overrides */
15
+ type DeepMergeSource<T> = T extends ReadonlyArray<any> ? ReadonlyArray<unknown> : T extends object ? { [K in keyof T]?: unknown; } : never;
16
16
  /** deep-merge b into a */
17
- declare function deepMerge<T extends DeepMergeable>(a: T, b: NoInfer<T> | DeepMergeSource<NoInfer<T>> | null | undefined, {
18
- arrayExtend,
19
- maxRecursion,
20
- clone
21
- }?: DeepieMergeOpts<T>): T;
22
- //#endregion
23
- export { deepMerge };
17
+ export declare function deepMerge<T extends DeepMergeable>(a: T, b: NoInfer<T> | DeepMergeSource<NoInfer<T>> | null | undefined, { arrayExtend, maxRecursion, clone }?: DeepieMergeOpts<T>): T;
18
+ //#endregion
package/dist/index.js CHANGED
@@ -8,28 +8,28 @@ function getType(obj) {
8
8
  return typeof obj;
9
9
  }
10
10
  /** deep-merge b into a */
11
- function deepMerge(a, b, { arrayExtend = false, maxRecursion = 20, clone = false } = {
12
- arrayExtend: false,
13
- maxRecursion: 10
14
- }) {
11
+ function deepMerge(a, b, { arrayExtend = false, maxRecursion = 20, clone = false } = {}) {
15
12
  return merge(clone ? typeof clone === "function" ? clone(a) : structuredClone(a) : a, b, arrayExtend, maxRecursion);
16
13
  }
14
+ function union(target, source) {
15
+ const set = new Set(target);
16
+ for (const value of source) set.add(value);
17
+ return Array.from(set);
18
+ }
17
19
  function merge(a, b, arrayExtend, maxRecursion) {
18
20
  if (maxRecursion === 0) return a;
19
- if (Array.isArray(a)) {
20
- if (Array.isArray(b)) return arrayExtend ? Array.from(new Set([...a, ...b])) : b;
21
- return b;
22
- }
21
+ if (Array.isArray(a)) return arrayExtend && Array.isArray(b) ? union(a, b) : b;
23
22
  if (Array.isArray(b)) return b;
24
23
  if (isObject(a) && isObject(b)) {
25
24
  const keys = Object.keys(b);
26
25
  for (let i = 0, len = keys.length; i < len; i++) {
27
26
  const key = keys[i];
28
- const typeA = getType(a[key]);
29
- if (typeA !== getType(b[key])) a[key] = b[key];
30
- else if (typeA === "array" && (Array.isArray(arrayExtend) ? arrayExtend.includes(key) : arrayExtend)) a[key] = Array.from(new Set([...a[key], ...b[key]]));
31
- else if (typeA === "object") a[key] = merge(a[key], b[key], arrayExtend, maxRecursion - 1);
32
- else a[key] = b[key];
27
+ const valueA = a[key];
28
+ if (key === "__proto__" || (key === "constructor" || key === "prototype") && isObject(valueA) && !Object.hasOwn(a, key)) continue;
29
+ const valueB = b[key];
30
+ if (isObject(valueA) && isObject(valueB)) a[key] = merge(valueA, valueB, arrayExtend, maxRecursion - 1);
31
+ else if (Array.isArray(valueA) && Array.isArray(valueB) && (Array.isArray(arrayExtend) ? arrayExtend.includes(key) : arrayExtend)) a[key] = union(valueA, valueB);
32
+ else a[key] = valueB === null && getType(valueA) === "object" ? valueA : valueB;
33
33
  }
34
34
  }
35
35
  return a;
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "deepie-merge",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Yay, another deep merge",
5
5
  "author": "silverwind <me@silverwind.io>",
6
- "repository": "silverwind/deepie-merge",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/silverwind/deepie-merge.git"
9
+ },
7
10
  "license": "BSD-2-Clause",
8
11
  "type": "module",
9
12
  "sideEffects": false,
@@ -17,19 +20,18 @@
17
20
  "node": ">=22"
18
21
  },
19
22
  "devDependencies": {
20
- "@types/node": "25.6.0",
21
- "@typescript/native-preview": "7.0.0-dev.20260507.1",
22
- "eslint": "10.3.0",
23
- "eslint-config-silverwind": "133.0.1",
24
- "jest-extended": "7.0.0",
25
- "tsdown": "0.22.0",
26
- "tsdown-config-silverwind": "3.0.0",
23
+ "@types/node": "26.6.3",
24
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
25
+ "eslint": "10.11.0",
26
+ "eslint-config-silverwind": "151.0.1",
27
+ "tsdown": "0.23.0",
28
+ "tsdown-config-silverwind": "4.1.2",
27
29
  "typescript": "6.0.3",
28
- "typescript-config-silverwind": "18.0.0",
29
- "updates": "17.16.9",
30
- "updates-config-silverwind": "3.0.1",
31
- "versions": "15.0.3",
32
- "vitest": "4.1.5",
33
- "vitest-config-silverwind": "11.3.3"
30
+ "typescript-config-silverwind": "21.0.1",
31
+ "updates": "19.0.2",
32
+ "updates-config-silverwind": "4.0.7",
33
+ "versions": "15.5.1",
34
+ "vitest": "5.0.2",
35
+ "vitest-config-silverwind": "12.0.5"
34
36
  }
35
37
  }