@tsed/core 8.44.5 → 8.44.7

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.
@@ -1,11 +1,6 @@
1
1
  import { classOf } from "./classOf.js";
2
2
  import { isDate } from "./isDate.js";
3
- import { isFunction } from "./isFunction.js";
4
- import { isNil } from "./isNil.js";
5
- import { isPrimitive } from "./isPrimitive.js";
6
3
  import { isRegExp } from "./isRegExp.js";
7
- import { isSymbol } from "./isSymbol.js";
8
- const isBasicType = (source) => isNil(source) || isPrimitive(source) || isSymbol(source) || isFunction(source);
9
4
  /**
10
5
  * Creates a deep clone of the provided value, preserving prototype chains and handling circular references.
11
6
  *
@@ -16,8 +11,8 @@ const isBasicType = (source) => isNil(source) || isPrimitive(source) || isSymbol
16
11
  * @since v7.0.0
17
12
  */
18
13
  export const deepClone = (source, stack = new WeakMap()) => {
19
- // provides an early exit for simple cases
20
- if (isBasicType(source)) {
14
+ // provides an early exit for simple cases. Inline typeof checks: this runs for every cloned value
15
+ if (source === null || typeof source !== "object" || source instanceof Boolean) {
21
16
  return source;
22
17
  }
23
18
  const stacked = stack.get(source);
@@ -40,7 +35,12 @@ export const deepClone = (source, stack = new WeakMap()) => {
40
35
  if (Array.isArray(source)) {
41
36
  const clone = [];
42
37
  stack.set(source, clone);
43
- source.forEach((item, idx) => (clone[idx] = deepClone(item, stack)));
38
+ // the length is read once and holes are skipped, as forEach does
39
+ for (let i = 0, length = source.length; i < length; i++) {
40
+ if (i in source) {
41
+ clone[i] = deepClone(source[i], stack);
42
+ }
43
+ }
44
44
  return clone;
45
45
  }
46
46
  if (source instanceof Map) {
@@ -57,20 +57,25 @@ export const deepClone = (source, stack = new WeakMap()) => {
57
57
  }
58
58
  const clone = Object.create(Reflect.getPrototypeOf(source));
59
59
  stack.set(source, clone);
60
- Reflect.ownKeys(source).forEach((key) => {
60
+ const keys = Reflect.ownKeys(source);
61
+ for (let i = 0; i < keys.length; i++) {
62
+ const key = keys[i];
61
63
  // respects property descriptors and the prototype chain more explicitly, which is important for objects with getter/setter.
62
- const descriptor = Object.getOwnPropertyDescriptor(source, key);
64
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
63
65
  if (descriptor) {
64
- if (!isFunction(descriptor.value)) {
65
- Object.defineProperty(clone, key, {
66
- ...descriptor,
67
- value: deepClone(descriptor.value, stack)
68
- });
66
+ // the descriptor is a new object: the cloned value can be written into it. Accessors are kept as is
67
+ if ("value" in descriptor && typeof descriptor.value !== "function") {
68
+ descriptor.value = deepClone(descriptor.value, stack);
69
+ }
70
+ // a plain data property that nothing in the prototype chain intercepts (setter, read-only, __proto__):
71
+ // an assignment defines the same property, faster than defineProperty
72
+ if (descriptor.writable && descriptor.enumerable && descriptor.configurable && !(key in clone)) {
73
+ clone[key] = descriptor.value;
69
74
  }
70
75
  else {
71
- Object.defineProperty(clone, key, descriptor);
76
+ Reflect.defineProperty(clone, key, descriptor);
72
77
  }
73
78
  }
74
- });
79
+ }
75
80
  return clone;
76
81
  };
@@ -1,7 +1,4 @@
1
1
  import { createInstance } from "./createInstance.js";
2
- import { isFunction } from "./isFunction.js";
3
- import { isPrimitive } from "./isPrimitive.js";
4
- import { isSymbol } from "./isSymbol.js";
5
2
  import { objectKeys } from "./objectKeys.js";
6
3
  /**
7
4
  * Creates a merge reducer using a custom comparator function.
@@ -19,18 +16,40 @@ export function mergeReducerBuilder(cb) {
19
16
  return collection;
20
17
  };
21
18
  }
22
- const defaultReducer = mergeReducerBuilder((a, b) => a === b);
23
- function getReducer({ reducers, parentKey }) {
19
+ const hasEnumerableOwn = Object.prototype.propertyIsEnumerable;
20
+ /**
21
+ * Same result as `mergeReducerBuilder((a, b) => a === b)`, but appends in place:
22
+ * `merge()` gives it its own copy of the source array.
23
+ */
24
+ function defaultInPlaceReducer(collection, value, options) {
25
+ const index = collection.indexOf(value);
26
+ if (index === -1) {
27
+ collection.push(value);
28
+ }
29
+ else {
30
+ collection[index] = deepMerge(collection[index], value, options);
31
+ }
32
+ return collection;
33
+ }
34
+ function getReducer(reducers, parentKey) {
24
35
  if (!reducers) {
25
- return defaultReducer;
36
+ return defaultInPlaceReducer;
26
37
  }
27
38
  if (parentKey && reducers[parentKey]) {
28
39
  return reducers[parentKey];
29
40
  }
30
- return reducers["default"] || defaultReducer;
41
+ return reducers["default"] || defaultInPlaceReducer;
31
42
  }
32
43
  function shouldReturnObj(obj, source) {
33
- return isPrimitive(obj) || isSymbol(obj) || isFunction(obj) || source === undefined;
44
+ // inline typeof checks: this runs for every merged value
45
+ const type = typeof obj;
46
+ return (type === "string" ||
47
+ type === "number" ||
48
+ type === "boolean" ||
49
+ type === "symbol" ||
50
+ type === "function" ||
51
+ obj instanceof Boolean ||
52
+ source === undefined);
34
53
  }
35
54
  function shouldReturnSource(obj, source) {
36
55
  return obj === undefined || obj === null || (obj === "" && source !== "");
@@ -45,6 +64,13 @@ function shouldReturnSource(obj, source) {
45
64
  * @see DeepMergeOptions
46
65
  */
47
66
  export function deepMerge(source, obj, options = {}) {
67
+ return merge(source, obj, options, options.parentKey, true);
68
+ }
69
+ /**
70
+ * The root call works with the caller's options. The nested merges share one copy of its own properties,
71
+ * the nested key travels as `parentKey`, and each nested reducer receives its own `{...options, parentKey}` copy.
72
+ */
73
+ function merge(source, obj, options, parentKey, isRoot) {
48
74
  if (shouldReturnSource(obj, source)) {
49
75
  return source;
50
76
  }
@@ -52,20 +78,33 @@ export function deepMerge(source, obj, options = {}) {
52
78
  return obj;
53
79
  }
54
80
  if (Array.isArray(source)) {
55
- const reducer = getReducer(options);
56
- return [].concat(obj).reduce((out, value) => reducer(out, value, options), [...source]);
81
+ const reducerOptions = isRoot ? options : { ...options, parentKey };
82
+ const reducer = getReducer(reducerOptions.reducers, parentKey);
83
+ return [].concat(obj).reduce((out, value) => reducer(out, value, reducerOptions), [...source]);
57
84
  }
58
- const newObj = createInstance(source);
59
- return [...objectKeys(source), ...objectKeys(obj)].reduce((out, key) => {
60
- const src = source && source[key];
61
- const value = deepMerge(src, obj && obj[key], {
62
- ...options,
63
- parentKey: key
64
- });
65
- if (options.cleanUndefinedProps && value === undefined) {
66
- return out;
85
+ const out = createInstance(source);
86
+ const sourceKeys = objectKeys(source);
87
+ const objKeys = objectKeys(obj);
88
+ const nestedOptions = isRoot ? { ...options } : options;
89
+ // the keys of obj missing from source, listed before any merge: alter() can add properties to source
90
+ const newKeys = [];
91
+ for (let i = 0; i < objKeys.length; i++) {
92
+ if (!hasEnumerableOwn.call(source, objKeys[i])) {
93
+ newKeys.push(objKeys[i]);
67
94
  }
68
- out[key] = options.alter?.(key, value) ?? value;
69
- return out;
70
- }, newObj);
95
+ }
96
+ for (let i = 0; i < sourceKeys.length; i++) {
97
+ mergeKey(out, source, obj, sourceKeys[i], options, nestedOptions);
98
+ }
99
+ for (let i = 0; i < newKeys.length; i++) {
100
+ mergeKey(out, source, obj, newKeys[i], options, nestedOptions);
101
+ }
102
+ return out;
103
+ }
104
+ function mergeKey(out, source, obj, key, options, nestedOptions) {
105
+ const value = merge(source && source[key], obj && obj[key], nestedOptions, key, false);
106
+ if (options.cleanUndefinedProps && value === undefined) {
107
+ return;
108
+ }
109
+ out[key] = options.alter?.(key, value) ?? value;
71
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsed/core",
3
- "version": "8.44.5",
3
+ "version": "8.44.7",
4
4
  "description": "Core module for Ts.ED Framework",
5
5
  "source": "./src/index.ts",
6
6
  "type": "module",
@@ -39,10 +39,10 @@
39
39
  "tslib": ">=2.7.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@tsed/barrels": "8.44.5",
42
+ "@tsed/barrels": "8.44.7",
43
43
  "@tsed/monorepo-utils": "3.3.3",
44
- "@tsed/typescript": "8.44.5",
45
- "@tsed/vitest": "8.44.5",
44
+ "@tsed/typescript": "8.44.7",
45
+ "@tsed/vitest": "8.44.7",
46
46
  "temporal-polyfill": "1.0.1",
47
47
  "typescript": "6.0.2",
48
48
  "vitest": "4.1.4"