@superhero/deep 4.1.0 → 4.2.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
@@ -145,63 +145,66 @@ npm test
145
145
 
146
146
  ```
147
147
  ▶ @superhero/deep/assign
148
- ✔ Assigns arrays correctly (1.993351ms)
149
- ✔ Assigns objects correctly (0.797187ms)
150
- ✔ Overwrites non-object properties correctly (0.311862ms)
151
- ✔ Handles undefined values correctly (0.67959ms)
148
+ ✔ Assigns arrays correctly (3.167398ms)
149
+ ✔ Assigns objects correctly (0.763768ms)
150
+ ✔ Overwrites non-object properties correctly (0.347727ms)
151
+ ✔ Handles undefined values correctly (0.521744ms)
152
+
152
153
  ▶ Descriptor properties
153
154
  ▶ Retains
154
- ✔ non-writable, non-configurable and non-enumarable (0.365558ms)
155
- ✔ writable but non-configurable and non-enumarable (0.384859ms)
156
- ✔ writable and configurable but non-enumarable (0.24546ms)
157
- ✔ Retains (1.395563ms)
155
+ ✔ non-writable, non-configurable and non-enumarable (0.474666ms)
156
+ ✔ writable but non-configurable and non-enumarable (0.649244ms)
157
+ ✔ writable and configurable but non-enumarable (0.298073ms)
158
+ ✔ Retains (1.918778ms)
159
+
158
160
  ▶ Assigns
159
- ✔ non-writable, non-configurable and non-enumarable (0.246451ms)
160
- ✔ Assigns (0.528402ms)
161
- ✔ Descriptor properties (2.438803ms)
162
- ✔ Merges nested arrays correctly (1.67104ms)
163
- ✔ Merges nested objects correctly (0.649403ms)
164
- Does not alter objects with no conflicts (0.219023ms)
165
- @superhero/deep/assign (10.784739ms)
161
+ ✔ non-writable, non-configurable and non-enumarable (0.485428ms)
162
+ ✔ Assigns (0.889839ms)
163
+ ✔ Descriptor properties (3.74915ms)
164
+
165
+ ✔ Merges nested arrays correctly (1.898073ms)
166
+ Merges nested objects correctly (0.761838ms)
167
+ Does not alter objects with no conflicts (0.277694ms)
168
+ ✔ @superhero/deep/assign (14.053485ms)
166
169
 
167
170
  ▶ @superhero/deep/clone
168
- ✔ Clones simple objects (3.898407ms)
169
- ✔ Clones nested objects (0.358366ms)
170
- ✔ Clones arrays (0.347023ms)
171
- ✔ Handles circular references (0.190668ms)
172
- ✔ Clones objects with null prototype (0.302189ms)
173
- ✔ @superhero/deep/clone (6.720191ms)
171
+ ✔ Clones simple objects (7.235299ms)
172
+ ✔ Clones nested objects (0.634553ms)
173
+ ✔ Clones arrays (0.650244ms)
174
+ ✔ Handles circular references (0.382144ms)
175
+ ✔ Clones objects with null prototype (0.375334ms)
176
+ ✔ @superhero/deep/clone (11.89498ms)
174
177
 
175
178
  ▶ @superhero/deep/freeze
176
- ✔ Freezes a simple object (2.561531ms)
177
- ✔ Freezes nested objects recursively (0.357866ms)
178
- ✔ Handles circular references gracefully (0.308297ms)
179
- ✔ Freezes objects with symbols (0.210516ms)
180
- ✔ Handles already frozen objects without error (0.157051ms)
181
- ✔ Freezes objects with non-enumerable properties (0.214033ms)
182
- ✔ Freezes arrays (0.244445ms)
183
- ✔ Handles objects with null prototype (0.337528ms)
184
- ✔ Freezes objects with multiple property types (0.525787ms)
185
- ✔ @superhero/deep/freeze (7.261487ms)
179
+ ✔ Freezes a simple object (3.683567ms)
180
+ ✔ Freezes nested objects recursively (0.406919ms)
181
+ ✔ Handles circular references gracefully (0.309979ms)
182
+ ✔ Freezes objects with symbols (0.405137ms)
183
+ ✔ Handles already frozen objects without error (0.337038ms)
184
+ ✔ Freezes objects with non-enumerable properties (0.354681ms)
185
+ ✔ Freezes arrays (0.662049ms)
186
+ ✔ Handles objects with null prototype (0.387428ms)
187
+ ✔ Freezes objects with multiple property types (0.626902ms)
188
+ ✔ @superhero/deep/freeze (10.607326ms)
186
189
 
187
190
  ▶ @superhero/deep
188
- ✔ All functions are accessible as a member to the default import object (1.250244ms)
189
- ✔ All functions are accessible to import from the default import object (0.23689ms)
190
- ✔ @superhero/deep (3.080305ms)
191
+ ✔ All functions are accessible as a member to the default import object (1.734551ms)
192
+ ✔ All functions are accessible to import from the default import object (0.255022ms)
193
+ ✔ @superhero/deep (5.188429ms)
191
194
 
192
195
  ▶ @superhero/deep/merge
193
- ✔ Merges arrays with unique values (3.122153ms)
194
- ✔ Merges arrays with order preserved (0.298964ms)
195
- ✔ Handles empty arrays correctly (0.275066ms)
196
- ✔ Handles arrays with duplicate values (0.492229ms)
197
- ✔ Merges objects and prioritizes restrictive descriptors (0.623443ms)
198
- ✔ Merges objects with non-enumerable properties (0.342317ms)
199
- ✔ Handles nested object merging (0.43726ms)
200
- ✔ Stops at circular references (0.827394ms)
201
- ✔ Stops when nested and with circular references (0.720571ms)
202
- ✔ Returns second value for non-object types (0.925228ms)
203
- ✔ Handles multiple merges sequentially (0.359582ms)
204
- ✔ @superhero/deep/merge (12.298476ms)
196
+ ✔ Merges arrays with unique values (3.271451ms)
197
+ ✔ Merges arrays with order preserved (0.455376ms)
198
+ ✔ Handles empty arrays correctly (0.283857ms)
199
+ ✔ Handles arrays with duplicate values (0.538911ms)
200
+ ✔ Merges objects and prioritizes restrictive descriptors (0.592729ms)
201
+ ✔ Merges objects with non-enumerable properties (0.343159ms)
202
+ ✔ Handles nested object merging (0.359878ms)
203
+ ✔ Stops at circular references (0.554345ms)
204
+ ✔ Stops when nested and with circular references (0.667265ms)
205
+ ✔ Returns second value for non-object types (0.744991ms)
206
+ ✔ Handles multiple merges sequentially (0.327608ms)
207
+ ✔ @superhero/deep/merge (11.803686ms)
205
208
 
206
209
  tests 38
207
210
  suites 8
@@ -212,8 +215,8 @@ file | line % | branch % | funcs % | uncovered lines
212
215
  -----------------------------------------------------------------
213
216
  assign.js | 100.00 | 100.00 | 100.00 |
214
217
  assign.test.js | 100.00 | 100.00 | 100.00 |
215
- clone.js | 100.00 | 100.00 | 100.00 |
216
- clone.test.js | 96.34 | 87.50 | 100.00 | 56-58
218
+ clone.js | 95.83 | 92.86 | 100.00 | 22-23
219
+ clone.test.js | 100.00 | 100.00 | 100.00 |
217
220
  freeze.js | 100.00 | 100.00 | 100.00 |
218
221
  freeze.test.js | 100.00 | 100.00 | 100.00 |
219
222
  index.js | 100.00 | 100.00 | 100.00 |
@@ -221,7 +224,7 @@ index.test.js | 100.00 | 100.00 | 100.00 |
221
224
  merge.js | 100.00 | 100.00 | 100.00 |
222
225
  merge.test.js | 100.00 | 100.00 | 100.00 |
223
226
  -----------------------------------------------------------------
224
- all files | 99.66 | 99.19 | 100.00 |
227
+ all files | 99.78 | 99.25 | 100.00 |
225
228
  -----------------------------------------------------------------
226
229
  ```
227
230
 
package/clone.js CHANGED
@@ -1,6 +1,48 @@
1
- export default function clone(a, legacy = false)
1
+ export default function clone(input)
2
2
  {
3
- return structuredClone && false === legacy
4
- ? structuredClone(a)
5
- : JSON.parse(JSON.stringify(a))
3
+ const seen = new WeakSet()
4
+ return deepClone(input, seen)
5
+ }
6
+
7
+ function deepClone(value, seen)
8
+ {
9
+ switch(Object.prototype.toString.call(value))
10
+ {
11
+ case '[object Array]' : return cloneArray(value, seen)
12
+ case '[object Object]' : return cloneObject(value, seen)
13
+ }
14
+
15
+ return structuredClone(value)
16
+ }
17
+
18
+ function cloneArray(array, seen)
19
+ {
20
+ if(seen.has(array))
21
+ {
22
+ return array
23
+ }
24
+
25
+ seen.add(array)
26
+ return array.map((item) => deepClone(item, seen))
27
+ }
28
+
29
+ function cloneObject(obj, seen)
30
+ {
31
+ if(seen.has(obj))
32
+ {
33
+ return obj
34
+ }
35
+
36
+ seen.add(obj)
37
+
38
+ const output = {}
39
+
40
+ for(const key of Object.getOwnPropertyNames(obj))
41
+ {
42
+ const descriptor = Object.getOwnPropertyDescriptor(obj, key)
43
+ Object.defineProperty(output, key,
44
+ { ...descriptor, value : deepClone(descriptor.value, seen) })
45
+ }
46
+
47
+ return output
6
48
  }
package/clone.test.js CHANGED
@@ -8,75 +8,76 @@ suite('@superhero/deep/clone', () =>
8
8
  {
9
9
  const
10
10
  obj = { foo: 'bar', baz: 42 },
11
- result = deepclone(obj),
12
- legacy = deepclone(obj, true)
11
+ cloned = deepclone(obj)
13
12
 
14
- assert.deepStrictEqual(result, obj, 'Cloned object should be equal to the original')
15
- assert.deepStrictEqual(legacy, obj, 'Cloned object should be equal to the original (legacy mode)')
16
-
17
- assert.notStrictEqual(result, obj, 'Cloned object should not be the same reference as the original')
18
- assert.notStrictEqual(legacy, obj, 'Cloned object should not be the same reference as the original (legacy mode)')
13
+ assert.deepStrictEqual(cloned, obj, 'Cloned object should be equal to the original')
14
+ assert.notStrictEqual(cloned, obj, 'Not the same reference as the original')
19
15
  })
20
16
 
21
17
  test('Clones nested objects', () =>
22
18
  {
23
- const obj = { foo: { bar: { baz: 'qux' } } }
24
-
25
19
  const
26
- result = deepclone(obj),
27
- legacy = deepclone(obj, true)
20
+ obj = { foo: { bar: { baz: 'qux' } } },
21
+ cloned = deepclone(obj)
22
+
23
+ assert.deepStrictEqual(cloned, obj, 'Cloned nested object should be equal to the original')
24
+ assert.notStrictEqual(cloned.foo, obj.foo, 'Not the same reference as the original')
25
+ })
26
+
27
+ test('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)
37
+
38
+ assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
39
+ assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
40
+ assert.notStrictEqual(cloned.foo, origin.foo, 'Cloned nested object should not share reference with the original')
28
41
 
29
- assert.deepStrictEqual(result, obj, 'Cloned nested object should be equal to the original')
30
- assert.deepStrictEqual(legacy, obj, 'Cloned nested object should be equal to the original (legacy mode)')
42
+ const
43
+ clonedDescriptors = Object.getOwnPropertyDescriptors(cloned),
44
+ originDescriptors = Object.getOwnPropertyDescriptors(origin)
31
45
 
32
- assert.notStrictEqual(result.foo, obj.foo, 'Cloned nested object should not share reference with the original')
33
- assert.notStrictEqual(legacy.foo, obj.foo, 'Cloned nested object should not share reference with the original (legacy mode)')
46
+ for(const key in originDescriptors)
47
+ {
48
+ assert.equal(clonedDescriptors[key].enumerable, originDescriptors[key].enumerable)
49
+ assert.equal(clonedDescriptors[key].writable, originDescriptors[key].writable)
50
+ assert.equal(clonedDescriptors[key].configurable, originDescriptors[key].configurable)
51
+ }
34
52
  })
35
53
 
36
54
  test('Clones arrays', () =>
37
55
  {
38
- const arr = [1, 2, 3, [4, 5]]
39
-
40
56
  const
41
- result = deepclone(arr),
42
- legacy = deepclone(arr, true)
57
+ array = [1, 2, 3, [4, 5]],
58
+ cloned = deepclone(array)
43
59
 
44
- assert.deepStrictEqual(result, arr, 'Cloned array should be equal to the original')
45
- assert.deepStrictEqual(legacy, arr, 'Cloned array should be equal to the original (legacy mode)')
60
+ assert.deepStrictEqual(cloned, array, 'Cloned array should be equal to the original')
61
+ assert.notStrictEqual(cloned, array, 'Not the same reference as the original')
62
+ assert.notStrictEqual(cloned[3], array[3], 'Nested array in clone should not share reference with the original')
63
+ })
46
64
 
47
- assert.notStrictEqual(result, arr, 'Cloned array should not share reference with the original')
48
- assert.notStrictEqual(legacy, arr, 'Cloned array should not share reference with the original (legacy mode)')
65
+ test('Handles circular references', () =>
66
+ {
67
+ const obj = {}
68
+ obj.self = obj
49
69
 
50
- assert.notStrictEqual(result[3], arr[3], 'Nested array in clone should not share reference with the original')
51
- assert.notStrictEqual(legacy[3], arr[3], 'Nested array in clone should not share reference with the original (legacy mode)')
70
+ const cloned = deepclone(obj)
71
+ assert.strictEqual(cloned.self, obj, 'Circular references should be preserved in the clone')
52
72
  })
53
73
 
54
- if(false === !!structuredClone)
55
- {
56
- test.skip('Handles circular references (structuredClone not available)')
57
- test.skip('Clones objects with null prototype (structuredClone not available)')
58
- }
59
- else
74
+ test('Clones objects with null prototype', () =>
60
75
  {
61
- test('Handles circular references', () =>
62
- {
63
- const obj = {}
64
- obj.self = obj
65
-
66
- const result = deepclone(obj)
67
-
68
- assert.strictEqual(result.self, result, 'Circular references should be preserved in the clone')
69
- })
70
-
71
- test('Clones objects with null prototype', () =>
72
- {
73
- const obj = Object.create(null)
74
- obj.foo = 'bar'
75
-
76
- const result = deepclone(obj)
77
-
78
- assert.deepEqual(result, obj, 'Cloned object with null prototype should be equal to the original')
79
- assert.notStrictEqual(result, obj, 'Cloned object with null prototype should not share reference with the original')
80
- })
81
- }
76
+ const obj = Object.create(null)
77
+ obj.foo = 'bar'
78
+
79
+ const cloned = deepclone(obj)
80
+ assert.deepEqual(cloned, obj, 'Cloned object with null prototype should be equal to the original')
81
+ assert.notStrictEqual(cloned, obj, 'Not the same reference as the original')
82
+ })
82
83
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhero/deep",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "A collection of deep structure operations",
5
5
  "keywords": ["deep", "assign", "clone", "freeze", "merge"],
6
6
  "main": "index.js",