@ditojs/utils 1.20.0 → 1.22.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/utils",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "description": "Dito.js Utility Functions – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/utils",
@@ -29,11 +29,11 @@
29
29
  "ie_mob >= 11"
30
30
  ],
31
31
  "dependencies": {
32
- "punycode": "^2.1.1"
32
+ "punycode": "^2.3.0"
33
33
  },
34
34
  "devDependencies": {
35
- "typescript": "^4.9.3"
35
+ "typescript": "^4.9.4"
36
36
  },
37
37
  "types": "types",
38
- "gitHead": "049aa3611927f3f096bc6230f0ce93e1419303e4"
38
+ "gitHead": "e1693edb8bf18e074606a842f2f2bacc1d2856cb"
39
39
  }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  isArray, isObject, isDate, isRegExp, isFunction, isPromise
3
3
  } from '../base/index.js'
4
- import { pick } from '../object/index.js'
4
+ import { pick } from './pick.js'
5
5
 
6
- export function clone(arg, options) {
6
+ export function clone(value, options) {
7
7
  const {
8
8
  shallow = false,
9
9
  enumerable = true,
@@ -14,44 +14,58 @@ export function clone(arg, options) {
14
14
  ? { processValue: options } // TODO: `callback` deprecated in December 2022.
15
15
  : options || {}
16
16
 
17
- const handleValue = value => shallow || transferables?.includes(value)
18
- ? value
19
- : clone(value, options)
17
+ const clones = new Map()
20
18
 
21
- let copy = arg
22
- if (isDate(arg)) {
23
- copy = new arg.constructor(+arg)
24
- } else if (isRegExp(arg)) {
25
- copy = new arg.constructor(arg)
26
- } else if (isArray(arg)) {
27
- copy = new arg.constructor(arg.length)
28
- for (let i = 0, l = arg.length; i < l; i++) {
29
- copy[i] = handleValue(arg[i])
30
- }
31
- } else if (isObject(arg)) {
19
+ const storeClone = (value, copy) => {
20
+ clones.set(value, copy)
21
+ return copy
22
+ }
23
+
24
+ const handleValue = value => {
25
+ return shallow || transferables?.includes(value) ? value
26
+ : clones.has(value) ? clones.get(value)
27
+ : cloneValue(value)
28
+ }
29
+
30
+ const cloneValue = value => {
31
+ let copy = value
32
+ if (isDate(value)) {
33
+ copy = storeClone(value, new value.constructor(+value))
34
+ } else if (isRegExp(value)) {
35
+ copy = storeClone(value, new value.constructor(value))
36
+ } else if (isArray(value)) {
37
+ copy = storeClone(value, new value.constructor(value.length))
38
+ for (let i = 0, l = value.length; i < l; i++) {
39
+ copy[i] = handleValue(value[i])
40
+ }
41
+ } else if (isObject(value)) {
32
42
  // Rely on arg.clone() if it exists and assume it creates an actual clone.
33
- if (isFunction(arg.clone)) {
34
- copy = arg.clone(options)
35
- } else if (isPromise(arg)) {
43
+ if (isFunction(value.clone)) {
44
+ copy = storeClone(value, value.clone(options))
45
+ } else if (isPromise(value)) {
36
46
  // https://stackoverflow.com/questions/37063293/can-i-clone-a-promise
37
- copy = arg.then()
38
- } else {
47
+ copy = storeClone(value, value.then())
48
+ } else {
39
49
  // Prevent calling the actual constructor since it is not guaranteed to
40
50
  // work as intended here, and only clone the non-inherited own properties.
41
- copy = Object.create(Object.getPrototypeOf(arg))
42
- const keys = enumerable ? Object.keys(arg) : Reflect.ownKeys(arg)
43
- for (const key of keys) {
44
- if (descriptors) {
45
- const desc = Reflect.getOwnPropertyDescriptor(arg, key)
46
- if (desc.value != null) {
47
- desc.value = handleValue(desc.value)
51
+ copy = storeClone(value, Object.create(Object.getPrototypeOf(value)))
52
+ clones.set(value, copy)
53
+ const keys = enumerable ? Object.keys(value) : Reflect.ownKeys(value)
54
+ for (const key of keys) {
55
+ if (descriptors) {
56
+ const desc = Reflect.getOwnPropertyDescriptor(value, key)
57
+ if (desc.value != null) {
58
+ desc.value = handleValue(desc.value)
59
+ }
60
+ Reflect.defineProperty(copy, key, desc)
61
+ } else {
62
+ copy[key] = handleValue(value[key])
48
63
  }
49
- Reflect.defineProperty(copy, key, desc)
50
- } else {
51
- copy[key] = handleValue(arg[key])
52
64
  }
53
65
  }
54
66
  }
67
+ return pick(processValue?.(copy), copy)
55
68
  }
56
- return pick(processValue?.(copy), copy)
69
+
70
+ return cloneValue(value)
57
71
  }
@@ -60,6 +60,22 @@ describe('clone()', () => {
60
60
  expect(copy).toStrictEqual({ b: 2 })
61
61
  })
62
62
 
63
+ it('should preserve identity', async () => {
64
+ const object = { a: { b: 1 } }
65
+ object.c = object.a
66
+ const copy = clone(object)
67
+ expect(copy).toEqual(object)
68
+ expect(copy.c).toBe(copy.a)
69
+ })
70
+
71
+ it('should handle circular references', async () => {
72
+ const object = { a: { b: 1 } }
73
+ object.c = object
74
+ const copy = clone(object)
75
+ expect(copy).toEqual(object)
76
+ expect(copy.c).toBe(copy)
77
+ })
78
+
63
79
  it('should handle non-enumerable properties', async () => {
64
80
  const object = { a: 1 }
65
81
  Object.defineProperty(object, 'b', { value: 2, enumerable: false })
package/types/index.d.ts CHANGED
@@ -108,9 +108,32 @@ export function asFunction<T>(
108
108
  /* --------------------------------- object --------------------------------- */
109
109
 
110
110
  /**
111
- * Performs a deep (recursive) clone on the supplied value.
111
+ * Clones the supplied value.
112
112
  */
113
- export function clone<T>(arg: T, iteratee?: (arg: any) => void): T
113
+ export function clone<T>(arg: T, options?: {
114
+ /**
115
+ * Wether to clone the value shallowly.
116
+ *
117
+ * @default false
118
+ */
119
+ shallow?: boolean,
120
+ /**
121
+ * Wether to clone all object properties, or only those that are defined
122
+ * directly on that object, and are not inherited from the object's prototype.
123
+ *
124
+ * @default true
125
+ */
126
+ enumerable?: boolean,
127
+ /**
128
+ * @default `true` if `options.enumerable` is true, `false` otherwise
129
+ */
130
+ descriptors?: boolean,
131
+ transferables?: any[],
132
+ /**
133
+ * Optional callback to process the cloned value.
134
+ */
135
+ processValue?: <S>(arg: T) => S,
136
+ }): T
114
137
 
115
138
  /**
116
139
  * Determines whether the supplied values can be considered equal through