mutate-cow 4.1.0 → 5.0.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/CowContext.mjs ADDED
@@ -0,0 +1,238 @@
1
+ /*
2
+ * Copyright (c) 2023 Michael Wiencek
3
+ *
4
+ * This source code is licensed under the MIT license. A copy can be found
5
+ * in the file named "LICENSE" at the root directory of this distribution.
6
+ */
7
+
8
+ import clone from './clone.mjs';
9
+ import {
10
+ STATUS_CHANGED,
11
+ STATUS_NONE,
12
+ STATUS_REVOKED,
13
+ } from './constants.mjs';
14
+
15
+ const STALE_VALUE = Object.freeze(Object.create(null));
16
+
17
+ export default class CowContext {
18
+ constructor(source, prop, root, parent) {
19
+ this._source = source;
20
+ this._prop = prop;
21
+ this._root = root || this;
22
+ this._parent = parent;
23
+ this._callbacks = [];
24
+ this._result = null;
25
+ this._status = STATUS_NONE;
26
+ this._children = null;
27
+ }
28
+
29
+ _copyForWrite() {
30
+ const status = this._status;
31
+ if (
32
+ status === STATUS_CHANGED ||
33
+ status === STATUS_REVOKED
34
+ ) {
35
+ return;
36
+ }
37
+ const stack = [];
38
+ let parent = this;
39
+ while (parent && parent._status !== STATUS_CHANGED) {
40
+ stack.push(parent);
41
+ parent = parent._parent;
42
+ }
43
+ for (let i = stack.length - 1; i >= 0; i--) {
44
+ const context = stack[i];
45
+ if (!context._result) {
46
+ context._result = clone(context._getSource(), context._callbacks);
47
+ }
48
+ if (context._parent) {
49
+ context._parent._result[context._prop] = context._result;
50
+ }
51
+ context._status = STATUS_CHANGED;
52
+ }
53
+ }
54
+
55
+ _getPropValue(prop) {
56
+ const target = this.read();
57
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
58
+ if (descriptor) {
59
+ if (descriptor.get) {
60
+ throw new Error('Getters are unsupported.');
61
+ }
62
+ if (descriptor.set) {
63
+ throw new Error('Setters are unsupported.');
64
+ }
65
+ return descriptor.value;
66
+ }
67
+ return Reflect.get(target, prop);
68
+ }
69
+
70
+ _getSource() {
71
+ let source = this._source;
72
+ if (source === STALE_VALUE) {
73
+ /*
74
+ * `this._parent` should always be defined here, because we only
75
+ * ever assign `STALE_VALUE` onto child contexts.
76
+ */
77
+ source = this._parent._getPropValue(this._prop);
78
+ this._source = source;
79
+ }
80
+ return source;
81
+ }
82
+
83
+ _throwIfRevoked() {
84
+ if (this.isRevoked()) {
85
+ throw new Error(
86
+ 'This context has been revoked and can no longer be used.',
87
+ );
88
+ }
89
+ }
90
+
91
+ read() {
92
+ this._throwIfRevoked();
93
+ return this._status === STATUS_CHANGED ? this._result : this._getSource();
94
+ }
95
+
96
+ write() {
97
+ this._throwIfRevoked();
98
+ this._copyForWrite();
99
+ return this._result;
100
+ }
101
+
102
+ _get(prop) {
103
+ const value = this._getPropValue(prop);
104
+
105
+ let children = this._children;
106
+ if (!children) {
107
+ children = new Map();
108
+ this._children = children;
109
+ }
110
+
111
+ let child = children.get(prop);
112
+ if (child) {
113
+ return child;
114
+ }
115
+
116
+ child = new CowContext(value, prop, this._root, this);
117
+ children.set(prop, child);
118
+ return child;
119
+ }
120
+
121
+ get(...props) {
122
+ let ctx = this;
123
+ for (const prop of props) {
124
+ ctx = ctx._get(prop);
125
+ }
126
+ return ctx;
127
+ }
128
+
129
+ _replace(value) {
130
+ const parent = this._parent;
131
+ if (parent) {
132
+ parent.set(this._prop, value);
133
+ } else {
134
+ this._source = value;
135
+ }
136
+ }
137
+
138
+ _set(prop, newValue) {
139
+ const origValue = this._getPropValue(prop);
140
+
141
+ if (!Object.is(origValue, newValue)) {
142
+ this._copyForWrite();
143
+ this._result[prop] = newValue;
144
+
145
+ // Child source values must be invalidated, because they can
146
+ // reference a previous copy we made.
147
+ const children = this._children;
148
+ if (children) {
149
+ const child = children.get(prop);
150
+ if (child) {
151
+ child._source = STALE_VALUE;
152
+ child._callbacks = [];
153
+ child._result = null;
154
+ child._status = STATUS_NONE;
155
+ }
156
+ }
157
+ }
158
+
159
+ return this;
160
+ }
161
+
162
+ set(...args) {
163
+ this._throwIfRevoked();
164
+ const newValue = args.pop();
165
+ const hasProps = args.length > 0;
166
+ const lastProp = hasProps ? args.pop() : undefined;
167
+ const ctx = hasProps ? this.get(...args) : this;
168
+ if (hasProps) {
169
+ ctx._set(lastProp, newValue);
170
+ } else {
171
+ ctx._replace(newValue);
172
+ }
173
+ return this;
174
+ }
175
+
176
+ update(...args) {
177
+ const updater = args.pop();
178
+ updater(this.get(...args));
179
+ return this;
180
+ }
181
+
182
+ parent() {
183
+ this._throwIfRevoked();
184
+ return this._parent;
185
+ }
186
+
187
+ root() {
188
+ this._throwIfRevoked();
189
+ return this._root;
190
+ }
191
+
192
+ revoke() {
193
+ if (this.isRevoked()) {
194
+ return;
195
+ }
196
+ if (this._parent) {
197
+ this._parent._children.delete(this._prop);
198
+ }
199
+ if (this._children) {
200
+ for (const child of this._children.values()) {
201
+ child.revoke();
202
+ }
203
+ this._children = null;
204
+ }
205
+ this._source = null;
206
+ this._prop = null;
207
+ this._root = null;
208
+ this._parent = null;
209
+ this._callbacks = null;
210
+ this._result = null;
211
+ this._status = STATUS_REVOKED;
212
+ }
213
+
214
+ isRevoked() {
215
+ return this._status === STATUS_REVOKED;
216
+ }
217
+
218
+ final() {
219
+ this._throwIfRevoked();
220
+ if (this._children) {
221
+ for (const child of this._children.values()) {
222
+ child.final();
223
+ }
224
+ }
225
+ const result = this.read();
226
+ const callbacks = this._callbacks;
227
+ this.revoke();
228
+ for (let i = 0; i < callbacks.length; i++) {
229
+ const {func, args} = callbacks[i];
230
+ func(...args);
231
+ }
232
+ return result;
233
+ }
234
+
235
+ finalRoot() {
236
+ return this.root().final();
237
+ }
238
+ }
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2019 Michael Wiencek
1
+ Copyright (c) 2023 Michael Wiencek
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,90 +3,141 @@
3
3
  ```JavaScript
4
4
  import mutate from 'mutate-cow';
5
5
 
6
- const animals = Object.freeze({
7
- cats: Object.freeze(['ragamuffin', 'shorthair', 'maine coon']),
6
+ const animals = deepFreeze({
7
+ cats: ['ragamuffin', 'shorthair', 'maine coon'],
8
8
  });
9
9
 
10
- const newAnimals = mutate(animals, copy => {
11
- copy.cats.push('bobtail');
12
- copy.dogs = Object.freeze(['hound']);
13
- });
10
+ const newAnimals = mutate(animals)
11
+ .set('dogs', ['hound'])
12
+ .update('cats', (ctx) => {
13
+ ctx.write().push('bobtail');
14
+ })
15
+ .final();
14
16
  ```
15
17
 
16
- This module allows you to update an immutable object as if it were mutable, inside a callback. It has copy-on-write semantics, so properties are only changed if you write to them. (In fact, if you perform no writes, the same object is returned back.) This makes it useful in conjuction with libraries like React, where state may be compared by reference.
17
-
18
- It's implemented using [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects, so browser support for that is required.
18
+ This module allows you to update an immutable object as if it were mutable. It has copy-on-write semantics, so properties are only changed if you write to them. (In fact, if you perform no writes, the same object is returned back.) This makes it useful in conjuction with libraries like React, where state may be compared by reference.
19
19
 
20
- While this doesn't appear to have been an original idea, I believe `mutate-cow` provides useful features that other packages don't:
20
+ `mutate-cow` provides useful features that other packages don't:
21
21
 
22
22
  * All property descriptors from the immutable object are preserved in the copy.
23
- * All extensibility information from the immutable object is preserved in the copy. Combined with the above point, this means that sealed objects stay sealed and frozen objects stay frozen. (Inside the callback, of course, the working copy in unsealed and all properties are writable.)
24
- * Getters and setters from the immutable object can be used inside the callback, and are preserved in the copy; they aren't converted from accessors to writables.
25
- * Arrays, objects, and class instances are supported for mutation. Inside the callback, these have the correct identities when passed to `Array.isArray` or `instanceof`.
26
- * Usable Flow types are provided. (The first type parameter must be a non-read-only variant of the input type.)
27
-
28
- For usage, please see [the tests](test.mjs).
23
+ * All extensibility information from the immutable object is preserved in the copy. Combined with the above point, this means that sealed objects stay sealed and frozen objects stay frozen.
24
+ * Arrays, objects, and class instances are supported for mutation.
25
+ * Flow and TypeScript definitions are provided.
29
26
 
30
27
  No cows were harmed in the making of this code.
31
28
 
32
- ## Caveats
29
+ ## API
33
30
 
34
- This module works for mutating plain objects, arrays, and user-defined classes. (Note, however, that class constructors are not called.) Other native, built-in object types are not supported for a variety reasons. For one, many of them can't be proxied correctly, because their methods aren't generic; i.e., they can't be called with a `this` value that's not of the exact object type. Another reason is that we'd need specialized code to clone each different type; they can't be created with `Object.create`, as the constructor must be called to define internal slots. We'd need further specialized code to intercept any methods that can write to any internal slots. That's just not feasible.
31
+ ### const ctx = mutate(source)
35
32
 
36
- Note that there's nothing wrong with creating *new* instances of these objects inside the callback and assigning them to properties. It's perfectly fine to clone them yourself. What's unsupported is mutating any existing objects of these types.
33
+ Returns a "context" object which can modify a copy of `source`.
37
34
 
38
- ```JavaScript
39
- const orig = {
40
- date: new Date(),
41
- string: new String('hello'),
42
- };
43
-
44
- mutate(orig, copy => {
45
- // These are unsupported, and will throw TypeErrors.
46
- copy.date.setFullYear(1999);
47
- copy.date.customProp = 'y';
48
- copy.string[0] = 'y';
49
-
50
- // These are fine.
51
- const newDate = new Date(orig.date.valueOf());
52
- newDate.setFullYear(1999);
53
- newDate.customProp = 'y';
54
- copy.date = newDate;
55
- copy.string = new String('yello');
56
- });
35
+ ```js
36
+ const foo = deepFreeze({bar: {baz: []}});
37
+ const ctx = mutate(foo);
38
+ ````
39
+
40
+ ### ctx.read()
41
+
42
+ Returns the current working copy of the context's `source` object, or just `source` if no changes were made.
43
+
44
+ ```js
45
+ ctx.read() === foo; // no changes
46
+ ctx.set('bar', 'baz', ['qux']);
47
+ ctx.read().bar.baz[0] === 'qux'; // changes
57
48
  ```
58
49
 
59
- ### Unwrapping proxied values
50
+ ### ctx.write()
60
51
 
61
- When you read from a property inside the callback, a `Proxy` is returned that can read from the source object (or working copy) and write to the copy. You may then wonder what happens if you perform `copy.foo = copy.bar`: will this assign a `Proxy`, or the value it targets? The answer in this case is the value it targets, because `mutate-cow` automatically unwraps proxied values on the RHS of assignments. However, suppose you did something like this instead:
52
+ Returns the current working copy of the context's `source` object. Makes a shallow copy of `source` first if no changes were made.
62
53
 
63
- ```JavaScript
64
- const orig = {foo: {value: 1}, bar: {value: 2}};
54
+ You normally don't need to call `write`. It's mainly useful for accessing methods on copied objects (e.g., array methods).
65
55
 
66
- const copy = mutate(orig, copy => {
67
- // this will assign an object resembling {value: Proxy}!
68
- copy.foo = {...copy.bar};
69
- });
56
+ ```js
57
+ ctx.get('bar', 'baz').write().push('qux');
58
+ ctx.read().bar.baz[0] === 'qux';
70
59
  ```
71
60
 
72
- In the above case, `copy.bar.value` is a `Proxy`, but `{...copy.bar}` (the assigned object) is not. `mutate-cow` doesn't *deeply* unwrap values, so you have to do it yourself in this case:
61
+ ### ctx.get(...path: [prop1, ...])
73
62
 
74
- ```JavaScript
75
- const orig = {foo: {value: 1}, bar: {value: 2}};
63
+ Returns a child context object for the given `path`.
76
64
 
77
- const copy = mutate(orig, (copy, unwrap) => {
78
- copy.foo = {...unwrap(copy.bar)};
79
- });
65
+ Passing zero arguments returns `ctx`.
66
+
67
+ ```js
68
+ ctx.get() === ctx;
69
+ ctx.get('bar').read() === foo.bar;
70
+ ctx.get('bar', 'baz').read() === '';
80
71
  ```
81
72
 
82
- If you don't do this, accessing `copy.foo.value` outside the callback will throw an error, because the `Proxy` will be revoked! An alternative which avoids this situation entirely is to just reference the source object where possible:
73
+ ### ctx.set(...path: [prop1, ...], value)
83
74
 
84
- ```JavaScript
85
- const orig = {foo: {value: 1}, bar: {value: 2}};
75
+ Sets the given `path` to `value` on the current working copy. Returns `ctx`.
86
76
 
87
- const copy = mutate(orig, copy => {
88
- copy.foo = {...orig.bar};
89
- });
77
+ Passing zero property names (i.e., only a value) sets the current context's value.
78
+
79
+ ```js
80
+ // these all do the same thing
81
+ ctx.set({bar: {baz: 2}});
82
+ ctx.set('bar', {baz: 2});
83
+ ctx.set('bar', 'baz', 2);
84
+ ctx.get('bar').set({baz: 2});
85
+ ctx.get('bar').set('baz', 2);
86
+ ctx.get('bar', 'baz').set(2);
90
87
  ```
91
88
 
92
- In fact, all `unwrap` does is return the underlying data from the source, or the working copy where you've made changes.
89
+ ### ctx.update(...path: [prop1, ...], updater)
90
+
91
+ Calls `updater(ctx.get(...path))` and returns `ctx`.
92
+
93
+ ```js
94
+ const copy = ctx
95
+ .update('bar', 'baz', (bazCtx) => {
96
+ bazCtx.write().push('qux');
97
+ })
98
+ .final();
99
+ copy.bar.baz[0] === 'qux';
100
+ ````
101
+
102
+ ### ctx.parent()
103
+
104
+ Returns the parent context of `ctx`.
105
+
106
+ ```js
107
+ ctx.parent() === null;
108
+ ctx.get('bar').parent() === ctx;
109
+ ctx.get('bar', 'baz').parent() === ctx.get('bar');
110
+ ````
111
+
112
+ ### ctx.root()
113
+
114
+ Returns the root context of `ctx`.
115
+
116
+ ```js
117
+ ctx.root() === ctx;
118
+ ctx.get('bar').root() === ctx;
119
+ ctx.get('bar', 'baz').root() === ctx;
120
+ ````
121
+
122
+ ### ctx.revoke()
123
+
124
+ Revokes `ctx` so that it can no longer be used. Returns `undefined`.
125
+
126
+ Attempting to use any method other than `isRevoked` on a revoked context will throw an error. This sets all internal properties to `null` so that there's no longer any reference to the `source` object or copy.
127
+
128
+ ### ctx.isRevoked()
129
+
130
+ Returns a boolean indicating whether `ctx` has been revoked.
131
+
132
+ ### ctx.final()
133
+
134
+ This is the same as `read`, except it also revokes the context and restores all property descriptors and extensibility information. This is what you call to get the final copy.
135
+
136
+ ```js
137
+ const copy = mutate(foo).set('bar', 'baz', 'qux').final();
138
+ Object.isFrozen(copy) === true; // since `foo` was frozen, `copy` will be too
139
+ ````
140
+
141
+ ### ctx.finalRoot()
142
+
143
+ Returns `ctx.root().final()`.
package/bench.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2019 Michael Wiencek
2
+ * Copyright (c) 2023 Michael Wiencek
3
3
  *
4
4
  * This source code is licensed under the MIT license. A copy can be found
5
5
  * in the file named "LICENSE" at the root directory of this distribution.
@@ -15,21 +15,52 @@ for (let i = 65, next = root; i <= 90; i++) {
15
15
  prop2: 2,
16
16
  prop3: 3,
17
17
  prop4: 4,
18
- prop4: 5,
18
+ prop5: 5,
19
19
  };
20
20
  }
21
21
 
22
- const updater1 = (newRoot) => {
23
- newRoot.root = true;
22
+ const updater1 = () => {
23
+ mutate(root).set('root', true).final();
24
24
  };
25
25
 
26
- const updater2 = (newRoot) => {
27
- newRoot.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.leaf = true;
26
+ const updater2 = () => {
27
+ mutate(root)
28
+ .set(
29
+ 'A',
30
+ 'B',
31
+ 'C',
32
+ 'D',
33
+ 'E',
34
+ 'F',
35
+ 'G',
36
+ 'H',
37
+ 'I',
38
+ 'J',
39
+ 'K',
40
+ 'L',
41
+ 'M',
42
+ 'N',
43
+ 'O',
44
+ 'P',
45
+ 'Q',
46
+ 'R',
47
+ 'S',
48
+ 'T',
49
+ 'U',
50
+ 'V',
51
+ 'W',
52
+ 'X',
53
+ 'Y',
54
+ 'Z',
55
+ 'leaf',
56
+ true,
57
+ )
58
+ .final();
28
59
  };
29
60
 
30
61
  function test() {
31
- mutate(root, updater1); // shallow update
32
- mutate(root, updater2); // deep update
62
+ updater1(); // shallow update
63
+ updater2(); // deep update
33
64
  }
34
65
 
35
66
  new Benchmark.Suite()
package/canClone.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2019 Michael Wiencek
2
+ * Copyright (c) 2023 Michael Wiencek
3
3
  *
4
4
  * This source code is licensed under the MIT license. A copy can be found
5
5
  * in the file named "LICENSE" at the root directory of this distribution.
@@ -9,14 +9,20 @@ const funcToString = Function.prototype.toString;
9
9
 
10
10
  const nativeCodeRegExp = /^function \w*\(\) \{\s*\[native code\]\s*\}$/m;
11
11
 
12
+ export const CANNOT_CLONE_ERROR =
13
+ 'Only plain objects, arrays, and class instances ' +
14
+ 'can be cloned. Primitives, functions, and built-ins ' +
15
+ 'are unsupported.';
16
+
12
17
  export default function canClone(object) {
13
18
  if (!object || typeof object !== 'object') {
14
- return false;
19
+ throw new Error(CANNOT_CLONE_ERROR);
15
20
  }
16
21
 
17
22
  let proto = Reflect.getPrototypeOf(object);
23
+
18
24
  while (proto) {
19
- const ctor = proto.constructor;
25
+ let ctor = proto.constructor;
20
26
  // A Generator object's constructor is an object.
21
27
  if (ctor && typeof ctor === 'object') {
22
28
  ctor = ctor.constructor;
@@ -26,11 +32,9 @@ export default function canClone(object) {
26
32
  ctor.name !== 'Array' &&
27
33
  ctor.name !== 'Object' &&
28
34
  nativeCodeRegExp.test(funcToString.call(ctor))) {
29
- return false;
35
+ throw new Error(CANNOT_CLONE_ERROR);
30
36
  }
31
37
 
32
38
  proto = Reflect.getPrototypeOf(proto);
33
39
  }
34
-
35
- return true;
36
40
  }
package/clone.mjs CHANGED
@@ -1,28 +1,47 @@
1
1
  /*
2
- * Copyright (c) 2019 Michael Wiencek
2
+ * Copyright (c) 2023 Michael Wiencek
3
3
  *
4
4
  * This source code is licensed under the MIT license. A copy can be found
5
5
  * in the file named "LICENSE" at the root directory of this distribution.
6
6
  */
7
7
 
8
+ import canClone from './canClone.mjs';
8
9
  import {
9
10
  NON_CONFIGURABLE,
10
11
  NON_CONFIGURABLE_AND_WRITABLE,
11
12
  NON_WRITABLE,
12
13
  } from './constants.mjs';
13
14
 
15
+ function isPrimitive(value) {
16
+ switch (typeof value) {
17
+ case 'bigint':
18
+ case 'boolean':
19
+ case 'number':
20
+ case 'string':
21
+ case 'symbol':
22
+ case 'undefined':
23
+ return true;
24
+ default:
25
+ return value === null;
26
+ }
27
+ }
28
+
14
29
  function restoreDescriptors(copy, changedDescriptors) {
15
30
  for (let i = 0; i < changedDescriptors.length; i++) {
16
31
  const [name, origDesc] = changedDescriptors[i];
17
- const desc = Reflect.getOwnPropertyDescriptor(copy, name);
18
- if (desc) {
19
- Object.assign(desc, origDesc);
20
- Reflect.defineProperty(copy, name, desc);
32
+ const descriptor = Reflect.getOwnPropertyDescriptor(copy, name);
33
+ if (descriptor) {
34
+ Object.assign(descriptor, origDesc);
35
+ Reflect.defineProperty(copy, name, descriptor);
21
36
  }
22
37
  }
23
38
  }
24
39
 
25
40
  export default function clone(source, callbacks) {
41
+ if (isPrimitive(source)) {
42
+ return source;
43
+ }
44
+ canClone(source);
26
45
  let changedDescriptors;
27
46
  let copy;
28
47
  if (Array.isArray(source)) {
@@ -33,15 +52,15 @@ export default function clone(source, callbacks) {
33
52
  const ownNames = Object.getOwnPropertyNames(source);
34
53
  for (let i = 0; i < ownNames.length; i++) {
35
54
  const name = ownNames[i];
36
- const desc = Reflect.getOwnPropertyDescriptor(source, name);
37
- const nonConfigurable = desc.configurable === false;
55
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, name);
56
+ const nonConfigurable = descriptor.configurable === false;
38
57
  let origDesc;
39
58
  if (nonConfigurable) {
40
- desc.configurable = true;
59
+ descriptor.configurable = true;
41
60
  origDesc = NON_CONFIGURABLE;
42
61
  }
43
- if (desc.writable === false) {
44
- desc.writable = true;
62
+ if (descriptor.writable === false) {
63
+ descriptor.writable = true;
45
64
  origDesc = nonConfigurable
46
65
  ? NON_CONFIGURABLE_AND_WRITABLE
47
66
  : NON_WRITABLE;
@@ -52,13 +71,29 @@ export default function clone(source, callbacks) {
52
71
  }
53
72
  changedDescriptors.push([name, origDesc]);
54
73
  }
55
- Reflect.defineProperty(copy, name, desc);
74
+ Reflect.defineProperty(copy, name, descriptor);
56
75
  }
57
76
  if (changedDescriptors) {
58
- callbacks.push([restoreDescriptors, copy, changedDescriptors]);
77
+ callbacks.push({
78
+ func: restoreDescriptors,
79
+ args: [copy, changedDescriptors],
80
+ });
59
81
  }
60
- if (!Reflect.isExtensible(source)) {
61
- callbacks.push([Reflect.preventExtensions, copy]);
82
+ if (Object.isFrozen(source)) {
83
+ callbacks.push({
84
+ func: Object.freeze,
85
+ args: [copy],
86
+ });
87
+ } else if (Object.isSealed(source)) {
88
+ callbacks.push({
89
+ func: Object.seal,
90
+ args: [copy],
91
+ });
92
+ } else if (!Reflect.isExtensible(source)) {
93
+ callbacks.push({
94
+ func: Reflect.preventExtensions,
95
+ args: [copy],
96
+ });
62
97
  }
63
98
  return copy;
64
99
  }