@superhero/deep 4.3.0 → 4.4.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/clone.js CHANGED
@@ -1,21 +1,26 @@
1
- export default function clone(input)
1
+ export default function clone(input, options = {})
2
2
  {
3
3
  const seen = new WeakSet()
4
- return deepClone(input, seen)
4
+
5
+ options.preservesImutable = options.preservesImutable ?? false
6
+ options.preservesEnumerable = options.preservesEnumerable ?? true
7
+ options.fallback = options.fallback ?? cloneFallback.bind(null, options, seen)
8
+
9
+ return deepClone(input, options, seen)
5
10
  }
6
11
 
7
- function deepClone(value, seen)
12
+ function deepClone(value, options, seen)
8
13
  {
9
14
  switch(Object.prototype.toString.call(value))
10
15
  {
11
- case '[object Array]' : return cloneArray(value, seen)
12
- case '[object Object]' : return cloneObject(value, seen)
16
+ case '[object Array]' : return cloneArray(value, options, seen)
17
+ case '[object Object]' : return cloneObject(value, options, seen)
13
18
  }
14
19
 
15
- return structuredClone(value)
20
+ return options.fallback(value)
16
21
  }
17
22
 
18
- function cloneArray(array, seen)
23
+ function cloneArray(array, options, seen)
19
24
  {
20
25
  if(seen.has(array))
21
26
  {
@@ -23,10 +28,10 @@ function cloneArray(array, seen)
23
28
  }
24
29
 
25
30
  seen.add(array)
26
- return array.map((item) => deepClone(item, seen))
31
+ return array.map((item) => deepClone(item, options, seen))
27
32
  }
28
33
 
29
- function cloneObject(obj, seen)
34
+ function cloneObject(obj, options, seen)
30
35
  {
31
36
  if(seen.has(obj))
32
37
  {
@@ -39,10 +44,52 @@ function cloneObject(obj, seen)
39
44
 
40
45
  for(const key of Object.getOwnPropertyNames(obj))
41
46
  {
42
- const descriptor = Object.getOwnPropertyDescriptor(obj, key)
43
- Object.defineProperty(output, key,
44
- { ...descriptor, value : deepClone(descriptor.value, seen) })
47
+ if(options.preservesImutable)
48
+ {
49
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key)
50
+ Object.defineProperty(output, key,
51
+ {
52
+ enumerable : options.preservesEnumerable ? descriptor.enumerable : true,
53
+ writable : descriptor.writable,
54
+ configurable : descriptor.configurable,
55
+ value : deepClone(descriptor.value, options, seen)
56
+ })
57
+ continue
58
+ }
59
+ else if(options.preservesEnumerable)
60
+ {
61
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key)
62
+ Object.defineProperty(output, key,
63
+ {
64
+ enumerable : descriptor.enumerable,
65
+ writable : true,
66
+ configurable : true,
67
+ value : deepClone(descriptor.value, options, seen)
68
+ })
69
+ continue
70
+ }
71
+ else
72
+ {
73
+ output[key] = deepClone(obj[key], options, seen)
74
+ }
45
75
  }
46
76
 
47
77
  return output
78
+ }
79
+
80
+ function cloneFallback(options, seen, value)
81
+ {
82
+ if(typeof value !== 'object'
83
+ || value === null)
84
+ {
85
+ return value
86
+ }
87
+
88
+ const clone = Object.create(Object.getPrototypeOf(value))
89
+ for(const key in value)
90
+ {
91
+ clone[key] = deepClone(value[key], options, seen)
92
+ }
93
+
94
+ return clone
48
95
  }
package/clone.test.js CHANGED
@@ -24,6 +24,32 @@ suite('@superhero/deep/clone', () =>
24
24
  assert.notStrictEqual(cloned.foo, obj.foo, 'Not the same reference as the original')
25
25
  })
26
26
 
27
+ test('Do not preserves descriptors', () =>
28
+ {
29
+ const origin = {}
30
+
31
+ Object.defineProperty(origin, 'foo', { value: {}, enumerable: true, writable: true, configurable: true })
32
+ Object.defineProperty(origin, 'bar', { value: {}, enumerable: false, writable: true, configurable: true })
33
+ Object.defineProperty(origin, 'baz', { value: {}, enumerable: false, writable: false, configurable: true })
34
+ Object.defineProperty(origin, 'qux', { value: {}, enumerable: false, writable: false, configurable: false })
35
+
36
+ const cloned = deepclone(origin, { preservesImutable: false, preservesEnumerable: false })
37
+
38
+ assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
39
+ assert.notStrictEqual(cloned.foo, origin.foo, 'Cloned nested object should not share reference with the original')
40
+
41
+ const
42
+ clonedDescriptors = Object.getOwnPropertyDescriptors(cloned),
43
+ originDescriptors = Object.getOwnPropertyDescriptors(origin)
44
+
45
+ for(const key in originDescriptors)
46
+ {
47
+ assert.equal(clonedDescriptors[key].enumerable, true)
48
+ assert.equal(clonedDescriptors[key].writable, true)
49
+ assert.equal(clonedDescriptors[key].configurable, true)
50
+ }
51
+ })
52
+
27
53
  test('Preserves descriptors', () =>
28
54
  {
29
55
  const origin = {}
@@ -33,7 +59,7 @@ suite('@superhero/deep/clone', () =>
33
59
  Object.defineProperty(origin, 'baz', { value: {}, enumerable: false, writable: false, configurable: true })
34
60
  Object.defineProperty(origin, 'qux', { value: {}, enumerable: false, writable: false, configurable: false })
35
61
 
36
- const cloned = deepclone(origin)
62
+ const cloned = deepclone(origin, { preservesImutable: true, preservesEnumerable: true })
37
63
 
38
64
  assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
39
65
  assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
@@ -51,6 +77,21 @@ suite('@superhero/deep/clone', () =>
51
77
  }
52
78
  })
53
79
 
80
+ test('Does not preserve frozen object state', () =>
81
+ {
82
+ const origin = { foo:'bar', baz: 42, qux: true }
83
+
84
+ Object.freeze(origin)
85
+
86
+ assert.ok(Object.isFrozen(origin), 'Origin object should be frozen')
87
+
88
+ const cloned = deepclone(origin)
89
+
90
+ assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
91
+ assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
92
+ assert.ok(false === Object.isFrozen(cloned), 'Cloned object should not be frozen')
93
+ })
94
+
54
95
  test('Clones arrays', () =>
55
96
  {
56
97
  const
package/merge.js CHANGED
@@ -66,7 +66,7 @@ export default function merge(a, b, ...c)
66
66
 
67
67
  return c.length
68
68
  ? merge(output, ...c)
69
- : deepclone(output)
69
+ : deepclone(output, { preservesImutable: true })
70
70
  }
71
71
 
72
72
  function mergeAandB(a, b, seen)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhero/deep",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "A collection of deep structure operations",
5
5
  "keywords": ["deep", "assign", "clone", "freeze", "intersect", "merge"],
6
6
  "main": "index.js",