mutate-cow 7.0.1 → 8.1.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 +9 -10
- package/package.json +2 -2
- package/src/index.js +238 -164
- package/src/index.js.flow +2 -0
- package/types/index.d.ts +2 -0
package/README.md
CHANGED
|
@@ -17,18 +17,11 @@ const newAnimals = mutate(animals)
|
|
|
17
17
|
|
|
18
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
|
-
`mutate-cow` provides useful features that other packages don't:
|
|
21
|
-
|
|
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.
|
|
24
|
-
* Arrays, objects, and class instances are supported for mutation.
|
|
25
|
-
* Flow and TypeScript definitions are provided.
|
|
26
|
-
|
|
27
20
|
No cows were harmed in the making of this code.
|
|
28
21
|
|
|
29
22
|
## API
|
|
30
23
|
|
|
31
|
-
### const ctx = mutate(source)
|
|
24
|
+
### const ctx = mutate(source, /* strict = */ false)
|
|
32
25
|
|
|
33
26
|
Returns a "context" object which can modify a copy of `source`.
|
|
34
27
|
|
|
@@ -37,6 +30,12 @@ const foo = deepFreeze({bar: {baz: []}});
|
|
|
37
30
|
const ctx = mutate(foo);
|
|
38
31
|
````
|
|
39
32
|
|
|
33
|
+
By default, you can mutate primitves, arrays, and plain objects. However, if `strict` is set to true, the following features are enabled:
|
|
34
|
+
|
|
35
|
+
* All property descriptors from the immutable object are preserved in the copy.
|
|
36
|
+
* 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.
|
|
37
|
+
* Class instances are supported for mutation.
|
|
38
|
+
|
|
40
39
|
### ctx.read()
|
|
41
40
|
|
|
42
41
|
Returns the current working copy of the context's `source` object, or just `source` if no changes were made.
|
|
@@ -132,10 +131,10 @@ Returns a boolean indicating whether `ctx` has been revoked.
|
|
|
132
131
|
|
|
133
132
|
### ctx.final()
|
|
134
133
|
|
|
135
|
-
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.
|
|
134
|
+
This is the same as `read`, except it also revokes the context, and in strict mode restores all property descriptors and extensibility information. This is what you call to get the final copy.
|
|
136
135
|
|
|
137
136
|
```js
|
|
138
|
-
const copy = mutate(foo).set('bar', 'baz', 'qux').final();
|
|
137
|
+
const copy = mutate(foo, /* strict = */ true).set('bar', 'baz', 'qux').final();
|
|
139
138
|
Object.isFrozen(copy) === true; // since `foo` was frozen, `copy` will be too
|
|
140
139
|
````
|
|
141
140
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutate-cow",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"description": "Update immutable objects as if they were mutable with copy-on-write",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"immutable",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"benchmark": "2.1.4",
|
|
21
|
-
"flow-bin": "0.
|
|
21
|
+
"flow-bin": "0.290.0",
|
|
22
22
|
"immer": "10.1.1",
|
|
23
23
|
"mutative": "1.2.0",
|
|
24
24
|
"tsd": "0.29.0"
|
package/src/index.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* in the file named "LICENSE" at the root directory of this distribution.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const EMPTY_OBJECT = Object.freeze({});
|
|
9
|
+
|
|
8
10
|
const NATIVE_CODE_REGEXP = /^function \w*\(\) \{\s*\[native code\]\s*\}$/m;
|
|
9
11
|
|
|
10
12
|
const STATUS_NONE = 1;
|
|
11
|
-
const
|
|
13
|
+
const STATUS_MUTABLE = 2;
|
|
12
14
|
const STATUS_REVOKED = 3;
|
|
13
15
|
const STATUS_STALE = 4;
|
|
14
16
|
|
|
@@ -20,40 +22,41 @@ function isPrimitive(value) {
|
|
|
20
22
|
return (type !== 'function' && type !== 'object');
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
function
|
|
24
|
-
if (
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
if (typeof value !== 'object') {
|
|
28
|
-
return 0;
|
|
25
|
+
function isCloneableObject(object) {
|
|
26
|
+
if (typeof object === 'function') {
|
|
27
|
+
return false;
|
|
29
28
|
}
|
|
30
|
-
let proto = Object.getPrototypeOf(
|
|
29
|
+
let proto = Object.getPrototypeOf(object);
|
|
31
30
|
while (proto) {
|
|
32
31
|
let ctor = proto.constructor;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ctor
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
if (ctor) {
|
|
33
|
+
// A Generator object's constructor is an object.
|
|
34
|
+
if (typeof ctor === 'object') {
|
|
35
|
+
ctor = ctor.constructor;
|
|
36
|
+
}
|
|
37
|
+
if (
|
|
38
|
+
typeof ctor === 'function' &&
|
|
39
|
+
ctor.name !== 'Array' &&
|
|
40
|
+
ctor.name !== 'Object' &&
|
|
41
|
+
NATIVE_CODE_REGEXP.test(Function.prototype.toString.call(ctor))
|
|
42
|
+
) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
46
|
proto = Object.getPrototypeOf(proto);
|
|
46
47
|
}
|
|
47
|
-
return
|
|
48
|
+
return true;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
function printConstructor(object) {
|
|
52
|
+
const ctor = object.constructor;
|
|
53
|
+
switch (typeof ctor) {
|
|
54
|
+
case 'function':
|
|
55
|
+
return ctor.name;
|
|
56
|
+
case 'object':
|
|
57
|
+
return Object.prototype.toString.call(ctor);
|
|
58
|
+
default:
|
|
59
|
+
return '[primitive constructor]';
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -68,120 +71,50 @@ function restoreDescriptors(copy, changedDescriptors) {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
function clone(source, callbacks) {
|
|
72
|
-
const cloneableType = getCloneableType(source);
|
|
73
|
-
throwIfTypeNotCloneable(cloneableType);
|
|
74
|
-
if (cloneableType === 1) {
|
|
75
|
-
return source;
|
|
76
|
-
}
|
|
77
|
-
const proto = Object.getPrototypeOf(source);
|
|
78
|
-
let changedDescriptors = [];
|
|
79
|
-
let copy;
|
|
80
|
-
if (Array.isArray(source)) {
|
|
81
|
-
copy = Reflect.construct(Array, source, proto.constructor);
|
|
82
|
-
} else {
|
|
83
|
-
copy = Object.create(proto);
|
|
84
|
-
}
|
|
85
|
-
const ownKeys = Reflect.ownKeys(source);
|
|
86
|
-
for (let i = 0; i < ownKeys.length; i++) {
|
|
87
|
-
const key = ownKeys[i];
|
|
88
|
-
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
89
|
-
let origDesc;
|
|
90
|
-
if (descriptor.configurable === false) {
|
|
91
|
-
descriptor.configurable = true;
|
|
92
|
-
origDesc = {configurable: false};
|
|
93
|
-
}
|
|
94
|
-
if (descriptor.writable === false) {
|
|
95
|
-
descriptor.writable = true;
|
|
96
|
-
origDesc = {...origDesc, writable: false};
|
|
97
|
-
}
|
|
98
|
-
if (origDesc) {
|
|
99
|
-
changedDescriptors.push([key, origDesc]);
|
|
100
|
-
}
|
|
101
|
-
Reflect.defineProperty(copy, key, descriptor);
|
|
102
|
-
}
|
|
103
|
-
if (changedDescriptors.length) {
|
|
104
|
-
callbacks.push({
|
|
105
|
-
func: restoreDescriptors,
|
|
106
|
-
args: [copy, changedDescriptors],
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
if (Object.isFrozen(source)) {
|
|
110
|
-
callbacks.push({
|
|
111
|
-
func: Object.freeze,
|
|
112
|
-
args: [copy],
|
|
113
|
-
});
|
|
114
|
-
} else if (Object.isSealed(source)) {
|
|
115
|
-
callbacks.push({
|
|
116
|
-
func: Object.seal,
|
|
117
|
-
args: [copy],
|
|
118
|
-
});
|
|
119
|
-
} else if (!Object.isExtensible(source)) {
|
|
120
|
-
callbacks.push({
|
|
121
|
-
func: Object.preventExtensions,
|
|
122
|
-
args: [copy],
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return copy;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
74
|
export class CowContext {
|
|
129
75
|
constructor(source, prop, parent) {
|
|
130
76
|
this._source = source;
|
|
131
77
|
this._prop = prop;
|
|
132
78
|
this._parent = parent;
|
|
133
|
-
this._callbacks = [];
|
|
134
79
|
this._result = null;
|
|
135
80
|
this._status = STATUS_NONE;
|
|
136
81
|
this._children = null;
|
|
137
82
|
}
|
|
138
83
|
|
|
139
|
-
_copyForWrite() {
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
status === STATUS_CHANGED ||
|
|
143
|
-
status === STATUS_REVOKED
|
|
144
|
-
) {
|
|
84
|
+
_copyForWrite(forceObject) {
|
|
85
|
+
if (this._status === STATUS_MUTABLE || this._status === STATUS_REVOKED) {
|
|
145
86
|
return;
|
|
146
87
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (context._parent) {
|
|
159
|
-
context._parent._result[context._prop] = context._result;
|
|
88
|
+
if (!this._result) {
|
|
89
|
+
const source = this._getSource();
|
|
90
|
+
if (isPrimitive(source)) {
|
|
91
|
+
this._result = forceObject ? {} : source;
|
|
92
|
+
} else if (!isCloneableObject(source)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
printConstructor(source) +
|
|
95
|
+
' objects are not supported for cloning.',
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
this._result = this._cloneSourceObject(source);
|
|
160
99
|
}
|
|
161
|
-
context._status = STATUS_CHANGED;
|
|
162
100
|
}
|
|
101
|
+
const parent = this._parent;
|
|
102
|
+
if (parent) {
|
|
103
|
+
parent._copyForWrite(/* forceObject = */ true);
|
|
104
|
+
parent._result[this._prop] = this._result;
|
|
105
|
+
}
|
|
106
|
+
this._status = STATUS_MUTABLE;
|
|
163
107
|
}
|
|
164
108
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (descriptor.get) {
|
|
169
|
-
throw new Error('Getters are unsupported.');
|
|
170
|
-
}
|
|
171
|
-
if (descriptor.set) {
|
|
172
|
-
throw new Error('Setters are unsupported.');
|
|
173
|
-
}
|
|
174
|
-
return descriptor;
|
|
109
|
+
_cloneSourceObject(source) {
|
|
110
|
+
if (Array.isArray(source)) {
|
|
111
|
+
return source.slice();
|
|
175
112
|
}
|
|
113
|
+
return {...source};
|
|
176
114
|
}
|
|
177
115
|
|
|
178
116
|
_getPropValue(prop) {
|
|
179
|
-
|
|
180
|
-
const descriptor = this._getPropDescriptor(target, prop);
|
|
181
|
-
if (descriptor) {
|
|
182
|
-
return descriptor.value;
|
|
183
|
-
}
|
|
184
|
-
return Reflect.get(target, prop);
|
|
117
|
+
return Reflect.get(this._read() ?? EMPTY_OBJECT, prop);
|
|
185
118
|
}
|
|
186
119
|
|
|
187
120
|
_getSource() {
|
|
@@ -204,14 +137,18 @@ export class CowContext {
|
|
|
204
137
|
}
|
|
205
138
|
}
|
|
206
139
|
|
|
140
|
+
_read() {
|
|
141
|
+
return this._status === STATUS_MUTABLE ? this._result : this._getSource();
|
|
142
|
+
}
|
|
143
|
+
|
|
207
144
|
read() {
|
|
208
145
|
this._throwIfRevoked();
|
|
209
|
-
return this.
|
|
146
|
+
return this._read();
|
|
210
147
|
}
|
|
211
148
|
|
|
212
149
|
write() {
|
|
213
150
|
this._throwIfRevoked();
|
|
214
|
-
this._copyForWrite();
|
|
151
|
+
this._copyForWrite(/* forceObject = */ false);
|
|
215
152
|
return this._result;
|
|
216
153
|
}
|
|
217
154
|
|
|
@@ -229,12 +166,13 @@ export class CowContext {
|
|
|
229
166
|
return child;
|
|
230
167
|
}
|
|
231
168
|
|
|
232
|
-
child = new
|
|
169
|
+
child = new this.constructor(value, prop, this);
|
|
233
170
|
children.set(prop, child);
|
|
234
171
|
return child;
|
|
235
172
|
}
|
|
236
173
|
|
|
237
174
|
get(...props) {
|
|
175
|
+
this._throwIfRevoked();
|
|
238
176
|
let ctx = this;
|
|
239
177
|
for (const prop of props) {
|
|
240
178
|
ctx = ctx._get(prop);
|
|
@@ -245,45 +183,67 @@ export class CowContext {
|
|
|
245
183
|
_replace(value) {
|
|
246
184
|
const parent = this._parent;
|
|
247
185
|
if (parent) {
|
|
248
|
-
parent.
|
|
186
|
+
parent._setIfChanged(this._prop, value);
|
|
249
187
|
} else {
|
|
250
188
|
this._source = value;
|
|
189
|
+
this._status = STATUS_NONE;
|
|
190
|
+
this._result = null;
|
|
191
|
+
// Child source values must be invalidated, because they can
|
|
192
|
+
// reference a previous copy we made.
|
|
193
|
+
this._setAllChildrenAsStale();
|
|
251
194
|
}
|
|
252
195
|
}
|
|
253
196
|
|
|
254
197
|
_set(prop, newValue) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
if (child) {
|
|
266
|
-
child._source = null;
|
|
267
|
-
child._callbacks = [];
|
|
268
|
-
child._result = null;
|
|
269
|
-
child._status = STATUS_STALE;
|
|
270
|
-
}
|
|
198
|
+
this._copyForWrite(/* forceObject = */ true);
|
|
199
|
+
this._result[prop] = newValue;
|
|
200
|
+
|
|
201
|
+
// Child source values must be invalidated, because they can
|
|
202
|
+
// reference a previous copy we made.
|
|
203
|
+
const children = this._children;
|
|
204
|
+
if (children) {
|
|
205
|
+
const child = children.get(prop);
|
|
206
|
+
if (child) {
|
|
207
|
+
child._setStale();
|
|
271
208
|
}
|
|
272
209
|
}
|
|
210
|
+
}
|
|
273
211
|
|
|
274
|
-
|
|
212
|
+
_setIfChanged(prop, newValue) {
|
|
213
|
+
const object = this._read() ?? EMPTY_OBJECT;
|
|
214
|
+
if (
|
|
215
|
+
!Object.hasOwn(object, prop) ||
|
|
216
|
+
!Object.is(Reflect.get(object, prop), newValue)
|
|
217
|
+
) {
|
|
218
|
+
this._set(prop, newValue);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_setStale() {
|
|
223
|
+
this._source = null;
|
|
224
|
+
this._result = null;
|
|
225
|
+
this._status = STATUS_STALE;
|
|
226
|
+
this._setAllChildrenAsStale();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
_setAllChildrenAsStale() {
|
|
230
|
+
const children = this._children;
|
|
231
|
+
if (children) {
|
|
232
|
+
for (const child of children.values()) {
|
|
233
|
+
child._setStale();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
275
236
|
}
|
|
276
237
|
|
|
277
238
|
set(...args) {
|
|
278
|
-
this._throwIfRevoked();
|
|
279
239
|
const newValue = args.pop();
|
|
280
240
|
const hasProps = args.length > 0;
|
|
281
|
-
const lastProp = hasProps ? args.pop() : undefined;
|
|
282
|
-
const ctx = hasProps ? this.get(...args) : this;
|
|
283
241
|
if (hasProps) {
|
|
284
|
-
|
|
242
|
+
const lastProp = args.pop();
|
|
243
|
+
this.get(...args)._setIfChanged(lastProp, newValue);
|
|
285
244
|
} else {
|
|
286
|
-
|
|
245
|
+
this._throwIfRevoked();
|
|
246
|
+
this._replace(newValue);
|
|
287
247
|
}
|
|
288
248
|
return this;
|
|
289
249
|
}
|
|
@@ -294,6 +254,21 @@ export class CowContext {
|
|
|
294
254
|
return this;
|
|
295
255
|
}
|
|
296
256
|
|
|
257
|
+
dangerouslySetAsMutable() {
|
|
258
|
+
this._throwIfRevoked();
|
|
259
|
+
const source = this._getSource();
|
|
260
|
+
// N.B. This may be (dangerously) equal to `source`.
|
|
261
|
+
const mutableValue = this._read();
|
|
262
|
+
const parent = this._parent;
|
|
263
|
+
if (parent) {
|
|
264
|
+
parent._set(this._prop, mutableValue);
|
|
265
|
+
}
|
|
266
|
+
this._source = source;
|
|
267
|
+
this._result = mutableValue;
|
|
268
|
+
this._status = STATUS_MUTABLE;
|
|
269
|
+
this._setAllChildrenAsStale();
|
|
270
|
+
}
|
|
271
|
+
|
|
297
272
|
parent() {
|
|
298
273
|
this._throwIfRevoked();
|
|
299
274
|
return this._parent;
|
|
@@ -308,27 +283,31 @@ export class CowContext {
|
|
|
308
283
|
return root;
|
|
309
284
|
}
|
|
310
285
|
|
|
311
|
-
|
|
286
|
+
_revoke(recursive) {
|
|
312
287
|
if (this.isRevoked()) {
|
|
313
288
|
return;
|
|
314
289
|
}
|
|
315
290
|
if (this._parent) {
|
|
316
291
|
this._parent._children.delete(this._prop);
|
|
317
292
|
}
|
|
318
|
-
if (this._children) {
|
|
319
|
-
|
|
320
|
-
|
|
293
|
+
if (recursive && this._children) {
|
|
294
|
+
const childrenToRevoke = [...this._children.values()];
|
|
295
|
+
for (const child of childrenToRevoke) {
|
|
296
|
+
child._revoke(true);
|
|
321
297
|
}
|
|
322
|
-
this._children = null;
|
|
323
298
|
}
|
|
299
|
+
this._children = null;
|
|
324
300
|
this._source = null;
|
|
325
301
|
this._prop = null;
|
|
326
302
|
this._parent = null;
|
|
327
|
-
this._callbacks = null;
|
|
328
303
|
this._result = null;
|
|
329
304
|
this._status = STATUS_REVOKED;
|
|
330
305
|
}
|
|
331
306
|
|
|
307
|
+
revoke() {
|
|
308
|
+
this._revoke(/* recursive = */ true);
|
|
309
|
+
}
|
|
310
|
+
|
|
332
311
|
isRevoked() {
|
|
333
312
|
return this._status === STATUS_REVOKED;
|
|
334
313
|
}
|
|
@@ -336,17 +315,13 @@ export class CowContext {
|
|
|
336
315
|
final() {
|
|
337
316
|
this._throwIfRevoked();
|
|
338
317
|
if (this._children) {
|
|
339
|
-
|
|
318
|
+
const childrenToFinalize = [...this._children.values()];
|
|
319
|
+
for (const child of childrenToFinalize) {
|
|
340
320
|
child.final();
|
|
341
321
|
}
|
|
342
322
|
}
|
|
343
|
-
const result = this.
|
|
344
|
-
|
|
345
|
-
this.revoke();
|
|
346
|
-
for (let i = 0; i < callbacks.length; i++) {
|
|
347
|
-
const {func, args} = callbacks[i];
|
|
348
|
-
func(...args);
|
|
349
|
-
}
|
|
323
|
+
const result = this._read();
|
|
324
|
+
this._revoke(/* recursive = */ false);
|
|
350
325
|
return result;
|
|
351
326
|
}
|
|
352
327
|
|
|
@@ -355,7 +330,106 @@ export class CowContext {
|
|
|
355
330
|
}
|
|
356
331
|
}
|
|
357
332
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
333
|
+
class CowContextStrict extends CowContext {
|
|
334
|
+
constructor(source, prop, parent) {
|
|
335
|
+
super(source, prop, parent);
|
|
336
|
+
this._callbacks = null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_replace(value) {
|
|
340
|
+
super._replace(value);
|
|
341
|
+
if (!this._parent) {
|
|
342
|
+
this._callbacks = null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
_setStale() {
|
|
347
|
+
super._setStale();
|
|
348
|
+
this._callbacks = null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_cloneSourceObject(source) {
|
|
352
|
+
if (!this._callbacks) {
|
|
353
|
+
this._callbacks = [];
|
|
354
|
+
}
|
|
355
|
+
const proto = Object.getPrototypeOf(source);
|
|
356
|
+
let changedDescriptors = [];
|
|
357
|
+
let copy;
|
|
358
|
+
if (Array.isArray(source)) {
|
|
359
|
+
copy = Reflect.construct(Array, source, proto.constructor);
|
|
360
|
+
} else {
|
|
361
|
+
copy = Object.create(proto);
|
|
362
|
+
}
|
|
363
|
+
const ownKeys = Reflect.ownKeys(source);
|
|
364
|
+
for (let i = 0; i < ownKeys.length; i++) {
|
|
365
|
+
const key = ownKeys[i];
|
|
366
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
367
|
+
let origDesc;
|
|
368
|
+
if (descriptor.configurable === false) {
|
|
369
|
+
descriptor.configurable = true;
|
|
370
|
+
origDesc = {configurable: false};
|
|
371
|
+
}
|
|
372
|
+
if (descriptor.writable === false) {
|
|
373
|
+
descriptor.writable = true;
|
|
374
|
+
origDesc = {...origDesc, writable: false};
|
|
375
|
+
}
|
|
376
|
+
if (origDesc) {
|
|
377
|
+
changedDescriptors.push([key, origDesc]);
|
|
378
|
+
}
|
|
379
|
+
Reflect.defineProperty(copy, key, descriptor);
|
|
380
|
+
}
|
|
381
|
+
if (changedDescriptors.length) {
|
|
382
|
+
this._callbacks.push({
|
|
383
|
+
func: restoreDescriptors,
|
|
384
|
+
args: [copy, changedDescriptors],
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (Object.isFrozen(source)) {
|
|
388
|
+
this._callbacks.push({
|
|
389
|
+
func: Object.freeze,
|
|
390
|
+
args: [copy],
|
|
391
|
+
});
|
|
392
|
+
} else if (Object.isSealed(source)) {
|
|
393
|
+
this._callbacks.push({
|
|
394
|
+
func: Object.seal,
|
|
395
|
+
args: [copy],
|
|
396
|
+
});
|
|
397
|
+
} else if (!Object.isExtensible(source)) {
|
|
398
|
+
this._callbacks.push({
|
|
399
|
+
func: Object.preventExtensions,
|
|
400
|
+
args: [copy],
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
return copy;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
dangerouslySetAsMutable() {
|
|
407
|
+
super.dangerouslySetAsMutable();
|
|
408
|
+
this._callbacks = null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
revoke() {
|
|
412
|
+
super.revoke();
|
|
413
|
+
this._callbacks = null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
final() {
|
|
417
|
+
const callbacks = this._callbacks;
|
|
418
|
+
const result = super.final();
|
|
419
|
+
if (callbacks) {
|
|
420
|
+
for (let i = 0; i < callbacks.length; i++) {
|
|
421
|
+
const {func, args} = callbacks[i];
|
|
422
|
+
func(...args);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export default function mutate(source, strict = false) {
|
|
430
|
+
if (strict) {
|
|
431
|
+
return new CowContextStrict(source, null, null);
|
|
432
|
+
} else {
|
|
433
|
+
return new CowContext(source, null, null);
|
|
434
|
+
}
|
|
361
435
|
}
|
package/src/index.js.flow
CHANGED
|
@@ -101,6 +101,7 @@ declare export class CowContext<
|
|
|
101
101
|
update<const K1: KeyType<T>, const K2: KeyType2<T, K1>, const K3: KeyType3<T, K1, K2>, const K4: KeyType4<T, K1, K2, K3>, const K5: KeyType5<T, K1, K2, K3, K4>, const K6: KeyType6<T, K1, K2, K3, K4, K5>>(prop1: K1, prop2: K2, prop3: K3, prop4: K4, prop5: K5, prop6: K6, updater: ($Get6<T, K1, K2, K3, K4, K5, K6, this>) => mixed): this;
|
|
102
102
|
update<const K1: KeyType<T>, const K2: KeyType2<T, K1>, const K3: KeyType3<T, K1, K2>, const K4: KeyType4<T, K1, K2, K3>, const K5: KeyType5<T, K1, K2, K3, K4>, const K6: KeyType6<T, K1, K2, K3, K4, K5>, const K7: KeyType7<T, K1, K2, K3, K4, K5, K6>>(prop1: K1, prop2: K2, prop3: K3, prop4: K4, prop5: K5, prop6: K6, prop7: K7, updater: ($Get7<T, K1, K2, K3, K4, K5, K6, K7, this>) => mixed): this;
|
|
103
103
|
update<const K1: KeyType<T>, const K2: KeyType2<T, K1>, const K3: KeyType3<T, K1, K2>, const K4: KeyType4<T, K1, K2, K3>, const K5: KeyType5<T, K1, K2, K3, K4>, const K6: KeyType6<T, K1, K2, K3, K4, K5>, const K7: KeyType7<T, K1, K2, K3, K4, K5, K6>, const K8: KeyType8<T, K1, K2, K3, K4, K5, K6, K7>>(prop1: K1, prop2: K2, prop3: K3, prop4: K4, prop5: K5, prop6: K6, prop7: K7, prop8: K8, updater: ($Get8<T, K1, K2, K3, K4, K5, K6, K7, K8, this>) => mixed): this;
|
|
104
|
+
dangerouslySetAsMutable(): void;
|
|
104
105
|
parent(): ParentContext;
|
|
105
106
|
root(): GetCowContextRoot<this>;
|
|
106
107
|
revoke(): void;
|
|
@@ -111,4 +112,5 @@ declare export class CowContext<
|
|
|
111
112
|
|
|
112
113
|
declare export default function mutate<T>(
|
|
113
114
|
source: T,
|
|
115
|
+
strict?: boolean,
|
|
114
116
|
): CowRootContext<T>;
|
package/types/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ declare class CowContext<
|
|
|
45
45
|
get<Path extends ReadonlyArray<PropertyKey>>(...path: Path): NestedContext<T, ParentContext, Path>;
|
|
46
46
|
set<Path extends ReadonlyArray<PropertyKey>>(...args: [...Path, NestedProp<T, Path>]): this;
|
|
47
47
|
update<Path extends ReadonlyArray<PropertyKey>>(...args: [...Path, (childContext: NestedContext<T, ParentContext, Path>) => unknown]): this;
|
|
48
|
+
dangerouslySetAsMutable(): void;
|
|
48
49
|
parent(): ParentContext;
|
|
49
50
|
root(): GetCowContextRoot<this>;
|
|
50
51
|
revoke(): void;
|
|
@@ -55,6 +56,7 @@ declare class CowContext<
|
|
|
55
56
|
|
|
56
57
|
declare function mutate<T>(
|
|
57
58
|
source: T,
|
|
59
|
+
strict?: boolean,
|
|
58
60
|
): CowRootContext<T>;
|
|
59
61
|
|
|
60
62
|
export {CowRootContext, CowAnyContext, CowContext};
|