mutate-cow 8.0.0 → 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/package.json +2 -2
- package/src/index.js +161 -139
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutate-cow",
|
|
3
|
-
"version": "8.
|
|
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,6 +5,8 @@
|
|
|
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;
|
|
@@ -27,20 +29,19 @@ function isCloneableObject(object) {
|
|
|
27
29
|
let proto = Object.getPrototypeOf(object);
|
|
28
30
|
while (proto) {
|
|
29
31
|
let ctor = proto.constructor;
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return false;
|
|
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
|
}
|
|
@@ -55,7 +56,7 @@ function printConstructor(object) {
|
|
|
55
56
|
case 'object':
|
|
56
57
|
return Object.prototype.toString.call(ctor);
|
|
57
58
|
default:
|
|
58
|
-
return '';
|
|
59
|
+
return '[primitive constructor]';
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -70,125 +71,50 @@ function restoreDescriptors(copy, changedDescriptors) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
function clone(context) {
|
|
74
|
-
const source = context._getSource();
|
|
75
|
-
if (isPrimitive(source)) {
|
|
76
|
-
return source;
|
|
77
|
-
}
|
|
78
|
-
if (!isCloneableObject(source)) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
printConstructor(source) +
|
|
81
|
-
' objects are not supported for cloning.',
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
if (context._strict) {
|
|
85
|
-
return cloneObjectStrict(source, context);
|
|
86
|
-
}
|
|
87
|
-
return cloneObjectLoose(source);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function cloneObjectLoose(source) {
|
|
91
|
-
if (Array.isArray(source)) {
|
|
92
|
-
return source.slice();
|
|
93
|
-
}
|
|
94
|
-
return {...source};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function cloneObjectStrict(source, context) {
|
|
98
|
-
if (!context._callbacks) {
|
|
99
|
-
context._callbacks = [];
|
|
100
|
-
}
|
|
101
|
-
const proto = Object.getPrototypeOf(source);
|
|
102
|
-
let changedDescriptors = [];
|
|
103
|
-
let copy;
|
|
104
|
-
if (Array.isArray(source)) {
|
|
105
|
-
copy = Reflect.construct(Array, source, proto.constructor);
|
|
106
|
-
} else {
|
|
107
|
-
copy = Object.create(proto);
|
|
108
|
-
}
|
|
109
|
-
const ownKeys = Reflect.ownKeys(source);
|
|
110
|
-
for (let i = 0; i < ownKeys.length; i++) {
|
|
111
|
-
const key = ownKeys[i];
|
|
112
|
-
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
113
|
-
let origDesc;
|
|
114
|
-
if (descriptor.configurable === false) {
|
|
115
|
-
descriptor.configurable = true;
|
|
116
|
-
origDesc = {configurable: false};
|
|
117
|
-
}
|
|
118
|
-
if (descriptor.writable === false) {
|
|
119
|
-
descriptor.writable = true;
|
|
120
|
-
origDesc = {...origDesc, writable: false};
|
|
121
|
-
}
|
|
122
|
-
if (origDesc) {
|
|
123
|
-
changedDescriptors.push([key, origDesc]);
|
|
124
|
-
}
|
|
125
|
-
Reflect.defineProperty(copy, key, descriptor);
|
|
126
|
-
}
|
|
127
|
-
if (changedDescriptors.length) {
|
|
128
|
-
context._callbacks.push({
|
|
129
|
-
func: restoreDescriptors,
|
|
130
|
-
args: [copy, changedDescriptors],
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
if (Object.isFrozen(source)) {
|
|
134
|
-
context._callbacks.push({
|
|
135
|
-
func: Object.freeze,
|
|
136
|
-
args: [copy],
|
|
137
|
-
});
|
|
138
|
-
} else if (Object.isSealed(source)) {
|
|
139
|
-
context._callbacks.push({
|
|
140
|
-
func: Object.seal,
|
|
141
|
-
args: [copy],
|
|
142
|
-
});
|
|
143
|
-
} else if (!Object.isExtensible(source)) {
|
|
144
|
-
context._callbacks.push({
|
|
145
|
-
func: Object.preventExtensions,
|
|
146
|
-
args: [copy],
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
return copy;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
74
|
export class CowContext {
|
|
153
|
-
constructor(source, prop, parent
|
|
75
|
+
constructor(source, prop, parent) {
|
|
154
76
|
this._source = source;
|
|
155
77
|
this._prop = prop;
|
|
156
78
|
this._parent = parent;
|
|
157
|
-
this._callbacks = null;
|
|
158
79
|
this._result = null;
|
|
159
80
|
this._status = STATUS_NONE;
|
|
160
81
|
this._children = null;
|
|
161
|
-
this._strict = strict;
|
|
162
82
|
}
|
|
163
83
|
|
|
164
|
-
_copyForWrite() {
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
status === STATUS_MUTABLE ||
|
|
168
|
-
status === STATUS_REVOKED
|
|
169
|
-
) {
|
|
84
|
+
_copyForWrite(forceObject) {
|
|
85
|
+
if (this._status === STATUS_MUTABLE || this._status === STATUS_REVOKED) {
|
|
170
86
|
return;
|
|
171
87
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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);
|
|
182
99
|
}
|
|
183
|
-
if (context._parent) {
|
|
184
|
-
context._parent._result[context._prop] = context._result;
|
|
185
|
-
}
|
|
186
|
-
context._status = STATUS_MUTABLE;
|
|
187
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;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_cloneSourceObject(source) {
|
|
110
|
+
if (Array.isArray(source)) {
|
|
111
|
+
return source.slice();
|
|
112
|
+
}
|
|
113
|
+
return {...source};
|
|
188
114
|
}
|
|
189
115
|
|
|
190
116
|
_getPropValue(prop) {
|
|
191
|
-
return Reflect.get(this._read(), prop);
|
|
117
|
+
return Reflect.get(this._read() ?? EMPTY_OBJECT, prop);
|
|
192
118
|
}
|
|
193
119
|
|
|
194
120
|
_getSource() {
|
|
@@ -222,7 +148,7 @@ export class CowContext {
|
|
|
222
148
|
|
|
223
149
|
write() {
|
|
224
150
|
this._throwIfRevoked();
|
|
225
|
-
this._copyForWrite();
|
|
151
|
+
this._copyForWrite(/* forceObject = */ false);
|
|
226
152
|
return this._result;
|
|
227
153
|
}
|
|
228
154
|
|
|
@@ -240,7 +166,7 @@ export class CowContext {
|
|
|
240
166
|
return child;
|
|
241
167
|
}
|
|
242
168
|
|
|
243
|
-
child = new
|
|
169
|
+
child = new this.constructor(value, prop, this);
|
|
244
170
|
children.set(prop, child);
|
|
245
171
|
return child;
|
|
246
172
|
}
|
|
@@ -261,7 +187,6 @@ export class CowContext {
|
|
|
261
187
|
} else {
|
|
262
188
|
this._source = value;
|
|
263
189
|
this._status = STATUS_NONE;
|
|
264
|
-
this._callbacks = null;
|
|
265
190
|
this._result = null;
|
|
266
191
|
// Child source values must be invalidated, because they can
|
|
267
192
|
// reference a previous copy we made.
|
|
@@ -270,7 +195,7 @@ export class CowContext {
|
|
|
270
195
|
}
|
|
271
196
|
|
|
272
197
|
_set(prop, newValue) {
|
|
273
|
-
this._copyForWrite();
|
|
198
|
+
this._copyForWrite(/* forceObject = */ true);
|
|
274
199
|
this._result[prop] = newValue;
|
|
275
200
|
|
|
276
201
|
// Child source values must be invalidated, because they can
|
|
@@ -285,9 +210,10 @@ export class CowContext {
|
|
|
285
210
|
}
|
|
286
211
|
|
|
287
212
|
_setIfChanged(prop, newValue) {
|
|
213
|
+
const object = this._read() ?? EMPTY_OBJECT;
|
|
288
214
|
if (
|
|
289
|
-
!Object.hasOwn(
|
|
290
|
-
!Object.is(
|
|
215
|
+
!Object.hasOwn(object, prop) ||
|
|
216
|
+
!Object.is(Reflect.get(object, prop), newValue)
|
|
291
217
|
) {
|
|
292
218
|
this._set(prop, newValue);
|
|
293
219
|
}
|
|
@@ -295,7 +221,6 @@ export class CowContext {
|
|
|
295
221
|
|
|
296
222
|
_setStale() {
|
|
297
223
|
this._source = null;
|
|
298
|
-
this._callbacks = null;
|
|
299
224
|
this._result = null;
|
|
300
225
|
this._status = STATUS_STALE;
|
|
301
226
|
this._setAllChildrenAsStale();
|
|
@@ -341,7 +266,6 @@ export class CowContext {
|
|
|
341
266
|
this._source = source;
|
|
342
267
|
this._result = mutableValue;
|
|
343
268
|
this._status = STATUS_MUTABLE;
|
|
344
|
-
this._callbacks = null;
|
|
345
269
|
this._setAllChildrenAsStale();
|
|
346
270
|
}
|
|
347
271
|
|
|
@@ -359,27 +283,31 @@ export class CowContext {
|
|
|
359
283
|
return root;
|
|
360
284
|
}
|
|
361
285
|
|
|
362
|
-
|
|
286
|
+
_revoke(recursive) {
|
|
363
287
|
if (this.isRevoked()) {
|
|
364
288
|
return;
|
|
365
289
|
}
|
|
366
290
|
if (this._parent) {
|
|
367
291
|
this._parent._children.delete(this._prop);
|
|
368
292
|
}
|
|
369
|
-
if (this._children) {
|
|
370
|
-
|
|
371
|
-
|
|
293
|
+
if (recursive && this._children) {
|
|
294
|
+
const childrenToRevoke = [...this._children.values()];
|
|
295
|
+
for (const child of childrenToRevoke) {
|
|
296
|
+
child._revoke(true);
|
|
372
297
|
}
|
|
373
|
-
this._children = null;
|
|
374
298
|
}
|
|
299
|
+
this._children = null;
|
|
375
300
|
this._source = null;
|
|
376
301
|
this._prop = null;
|
|
377
302
|
this._parent = null;
|
|
378
|
-
this._callbacks = null;
|
|
379
303
|
this._result = null;
|
|
380
304
|
this._status = STATUS_REVOKED;
|
|
381
305
|
}
|
|
382
306
|
|
|
307
|
+
revoke() {
|
|
308
|
+
this._revoke(/* recursive = */ true);
|
|
309
|
+
}
|
|
310
|
+
|
|
383
311
|
isRevoked() {
|
|
384
312
|
return this._status === STATUS_REVOKED;
|
|
385
313
|
}
|
|
@@ -387,13 +315,107 @@ export class CowContext {
|
|
|
387
315
|
final() {
|
|
388
316
|
this._throwIfRevoked();
|
|
389
317
|
if (this._children) {
|
|
390
|
-
|
|
318
|
+
const childrenToFinalize = [...this._children.values()];
|
|
319
|
+
for (const child of childrenToFinalize) {
|
|
391
320
|
child.final();
|
|
392
321
|
}
|
|
393
322
|
}
|
|
394
323
|
const result = this._read();
|
|
324
|
+
this._revoke(/* recursive = */ false);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
finalRoot() {
|
|
329
|
+
return this.root().final();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
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() {
|
|
395
417
|
const callbacks = this._callbacks;
|
|
396
|
-
|
|
418
|
+
const result = super.final();
|
|
397
419
|
if (callbacks) {
|
|
398
420
|
for (let i = 0; i < callbacks.length; i++) {
|
|
399
421
|
const {func, args} = callbacks[i];
|
|
@@ -402,12 +424,12 @@ export class CowContext {
|
|
|
402
424
|
}
|
|
403
425
|
return result;
|
|
404
426
|
}
|
|
405
|
-
|
|
406
|
-
finalRoot() {
|
|
407
|
-
return this.root().final();
|
|
408
|
-
}
|
|
409
427
|
}
|
|
410
428
|
|
|
411
429
|
export default function mutate(source, strict = false) {
|
|
412
|
-
|
|
430
|
+
if (strict) {
|
|
431
|
+
return new CowContextStrict(source, null, null);
|
|
432
|
+
} else {
|
|
433
|
+
return new CowContext(source, null, null);
|
|
434
|
+
}
|
|
413
435
|
}
|