mutate-cow 4.0.3 → 4.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 CHANGED
@@ -25,7 +25,7 @@ While this doesn't appear to have been an original idea, I believe `mutate-cow`
25
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
26
  * Usable Flow types are provided. (The first type parameter must be a non-read-only variant of the input type.)
27
27
 
28
- For usage, please see [the tests](test.js).
28
+ For usage, please see [the tests](test.mjs).
29
29
 
30
30
  No cows were harmed in the making of this code.
31
31
 
package/bench.mjs ADDED
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright (c) 2019 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 Benchmark from 'benchmark';
9
+ import mutate from './index.mjs';
10
+
11
+ const root = {};
12
+ for (let i = 65, next = root; i <= 90; i++) {
13
+ next = next[String.fromCharCode(i)] = {
14
+ prop1: 1,
15
+ prop2: 2,
16
+ prop3: 3,
17
+ prop4: 4,
18
+ prop4: 5,
19
+ };
20
+ }
21
+
22
+ const updater1 = (newRoot) => {
23
+ newRoot.root = true;
24
+ };
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;
28
+ };
29
+
30
+ function test() {
31
+ mutate(root, updater1); // shallow update
32
+ mutate(root, updater2); // deep update
33
+ }
34
+
35
+ new Benchmark.Suite()
36
+ .add('test', test)
37
+ .on('cycle', function (event) {
38
+ console.log(String(event.target));
39
+ })
40
+ .on('complete', function () {
41
+ console.log('Fastest is ' + this.filter('fastest').map('name'));
42
+ })
43
+ .run({async: true});
File without changes
@@ -9,7 +9,7 @@ import {
9
9
  NON_CONFIGURABLE,
10
10
  NON_CONFIGURABLE_AND_WRITABLE,
11
11
  NON_WRITABLE,
12
- } from './constants';
12
+ } from './constants.mjs';
13
13
 
14
14
  function restoreDescriptors(copy, changedDescriptors) {
15
15
  for (let i = 0; i < changedDescriptors.length; i++) {
File without changes
@@ -8,10 +8,10 @@
8
8
  import {
9
9
  CANNOT_CLONE_ERROR,
10
10
  PROXY_UNWRAP_KEY,
11
- } from './constants';
12
- import isObject from './isObject';
13
- import makeProxy, {Context} from './makeProxy';
14
- import unwrap from './unwrap';
11
+ } from './constants.mjs';
12
+ import isObject from './isObject.mjs';
13
+ import makeProxy, {Context} from './makeProxy.mjs';
14
+ import unwrap from './unwrap.mjs';
15
15
 
16
16
  export default function mutate(source, updater) {
17
17
  if (!isObject(source)) {
File without changes
File without changes
@@ -5,8 +5,8 @@
5
5
  * in the file named "LICENSE" at the root directory of this distribution.
6
6
  */
7
7
 
8
- import canClone from './canClone';
9
- import clone from './clone';
8
+ import canClone from './canClone.mjs';
9
+ import clone from './clone.mjs';
10
10
  import {
11
11
  CANNOT_CLONE_ERROR,
12
12
  CONFIGURABLE_AND_WRITABLE,
@@ -14,9 +14,9 @@ import {
14
14
  STATUS_CHANGED,
15
15
  STATUS_NONE,
16
16
  STATUS_REVOKED,
17
- } from './constants';
18
- import isObject from './isObject';
19
- import unwrap from './unwrap';
17
+ } from './constants.mjs';
18
+ import isObject from './isObject.mjs';
19
+ import unwrap from './unwrap.mjs';
20
20
 
21
21
  export function Context(root, parent, prop) {
22
22
  this.root = root || this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mutate-cow",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
4
4
  "description": "Update immutable objects as if they were mutable with copy-on-write",
5
5
  "keywords": [
6
6
  "immutable",
@@ -8,21 +8,18 @@
8
8
  "copy on write",
9
9
  "state management"
10
10
  ],
11
- "main": "index.js",
12
- "module": "main.js",
11
+ "main": "index.mjs",
13
12
  "license": "MIT",
14
13
  "author": "Michael Wiencek <mwtuea@gmail.com>",
15
14
  "repository": "github:mwiencek/mutate-cow",
16
15
  "devDependencies": {
17
16
  "benchmark": "2.1.4",
18
- "flow-bin": "0.126.1"
17
+ "flow-bin": "0.182.0"
19
18
  },
20
19
  "sideEffects": false,
21
20
  "scripts": {
22
- "test": "node -r esm test.js"
23
- },
24
- "dependencies": {
25
- "esm": "^3.2.25"
21
+ "test": "node test.mjs"
26
22
  },
23
+ "dependencies": {},
27
24
  "peerDependencies": {}
28
25
  }
package/test.mjs ADDED
@@ -0,0 +1,749 @@
1
+ /*
2
+ * @flow
3
+ * Copyright (c) 2019 Michael Wiencek
4
+ *
5
+ * This source code is licensed under the MIT license. A copy can be found
6
+ * in the file named "LICENSE" at the root directory of this distribution.
7
+ */
8
+
9
+ import mutate from './index.mjs';
10
+ import {PROXY_UNWRAP_KEY} from './constants.mjs';
11
+
12
+ function assert(truth, message) {
13
+ if (!truth) {
14
+ throw new Error('assertion failed');
15
+ }
16
+ }
17
+
18
+ /*::
19
+ type DatePeriod = {
20
+ year: number,
21
+ };
22
+
23
+ type ReadOnlyDatePeriod = {
24
+ +year: number,
25
+ };
26
+
27
+ type Person = {
28
+ name: string,
29
+ birth_date: DatePeriod,
30
+ death_date: DatePeriod,
31
+ };
32
+
33
+ type ReadOnlyPerson = {
34
+ +name: string,
35
+ +birth_date: ReadOnlyDatePeriod,
36
+ +death_date: ReadOnlyDatePeriod,
37
+ };
38
+
39
+ type People = Array<Person>;
40
+ type ReadOnlyPeople = $ReadOnlyArray<ReadOnlyPerson>;
41
+ */
42
+
43
+ const aliceBirthDate = {year: 2100};
44
+ const aliceDeathDate = {year: 2330};
45
+
46
+ const alice /*: ReadOnlyPerson */ = {
47
+ name: 'Alice',
48
+ birth_date: aliceBirthDate,
49
+ death_date: aliceDeathDate,
50
+ };
51
+
52
+ const frozenBob /*: ReadOnlyPerson */ = Object.freeze({
53
+ name: 'Bob',
54
+ birth_date: Object.freeze({year: 2450}),
55
+ death_date: Object.freeze({year: 2988}),
56
+ });
57
+
58
+ { // object not copied if no writes are made
59
+
60
+ const copy = mutate/*:: <Person, _>*/(alice, (copy) => {
61
+ assert('name' in copy);
62
+ assert('birth_date' in copy);
63
+ assert('death_date' in copy);
64
+ assert('year' in copy.birth_date);
65
+ assert('year' in copy.death_date);
66
+
67
+ copy.name;
68
+ copy.birth_date.year;
69
+ copy.death_date.year;
70
+
71
+ const properties = Object.getOwnPropertyNames(copy).sort();
72
+ assert(properties.length === 3);
73
+ assert(properties[0] === 'birth_date');
74
+ assert(properties[1] === 'death_date');
75
+ assert(properties[2] === 'name');
76
+ });
77
+
78
+ assert(alice === copy);
79
+ }
80
+
81
+ {
82
+ const copy = mutate/*:: <Person, _>*/(alice, (copy) => {
83
+ assert(copy.birth_date.year === 2100);
84
+ copy.birth_date.year = 1988;
85
+ assert(copy.birth_date.year === 1988);
86
+ });
87
+
88
+ assert(copy !== alice);
89
+ assert(copy.birth_date !== alice.birth_date);
90
+ assert(copy.birth_date.year === 1988);
91
+ assert(copy.death_date === aliceDeathDate);
92
+
93
+ assert(alice.name === 'Alice');
94
+ assert(alice.birth_date === aliceBirthDate);
95
+ assert(alice.birth_date.year === 2100);
96
+ assert(alice.death_date === aliceDeathDate);
97
+ assert(alice.death_date.year === 2330);
98
+ }
99
+
100
+ {
101
+ let copy = mutate/*:: <Person, _>*/(frozenBob, (copy) => {
102
+ copy.name;
103
+ copy.birth_date.year;
104
+ copy.death_date.year;
105
+ });
106
+
107
+ assert(copy === frozenBob);
108
+
109
+ copy = mutate/*:: <Person, _>*/(frozenBob, (copy) => {
110
+ assert(copy.birth_date.year === 2450);
111
+ copy.birth_date.year = 1988;
112
+ assert(copy.birth_date.year === 1988);
113
+ });
114
+
115
+ assert(copy !== frozenBob);
116
+ // frozenness is preserved
117
+ assert(Object.isFrozen(copy));
118
+ assert(copy.birth_date !== frozenBob.birth_date);
119
+ assert(Object.isFrozen(copy.birth_date));
120
+ assert(copy.birth_date.year === 1988);
121
+ assert(copy.death_date === frozenBob.death_date);
122
+ }
123
+
124
+ const people/*: ReadOnlyPeople */ = [alice, frozenBob];
125
+
126
+ { // object not copied if no writes are made
127
+
128
+ const copy = mutate/*:: <People, _>*/(people, (copy) => {
129
+ copy[0].death_date.year;
130
+ copy[1].death_date.year;
131
+ });
132
+
133
+ assert(people === copy);
134
+ assert(people[0] === copy[0]);
135
+ assert(people[1] === copy[1]);
136
+ assert(people[0].birth_date === copy[0].birth_date);
137
+ assert(people[1].death_date === copy[1].death_date);
138
+ }
139
+
140
+ { // object not copied if assignments don't change the underlying data
141
+
142
+ const copy = mutate/*:: <People, _>*/(people, (copy) => {
143
+ copy[0] = copy[0];
144
+ copy[0].birth_date = copy[0].birth_date;
145
+ copy[0].birth_date.year = copy[0].birth_date.year;
146
+ copy[0].birth_date.year = 2100;
147
+ });
148
+
149
+ assert(people === copy);
150
+ assert(people[0] === copy[0]);
151
+ assert(people[1] === copy[1]);
152
+ assert(people[0].birth_date === copy[0].birth_date);
153
+ assert(people[1].death_date === copy[1].death_date);
154
+ assert(copy[0].birth_date.year === 2100);
155
+ }
156
+
157
+ { // object delete
158
+
159
+ const orig = {};
160
+
161
+ Object.defineProperty(orig, 'foo', {
162
+ configurable: false,
163
+ enumerable: true,
164
+ writable: true,
165
+ value: 1,
166
+ });
167
+
168
+ Object.defineProperty(orig, 'bar', {
169
+ configurable: false,
170
+ enumerable: true,
171
+ writable: true,
172
+ value: 2,
173
+ });
174
+
175
+ Object.defineProperty(orig, 'baz', {
176
+ configurable: true,
177
+ enumerable: true,
178
+ writable: false,
179
+ value: 3,
180
+ });
181
+
182
+ const copy = mutate/*:: <{foo?: number, bar?: number, baz?: number}, _>*/(orig, (copy) => {
183
+ delete copy.foo;
184
+ delete copy.bar;
185
+ delete copy.baz;
186
+ });
187
+
188
+ assert(orig.foo === 1);
189
+ assert(orig.bar === 2);
190
+ assert(orig.baz === 3);
191
+ assert(!copy.hasOwnProperty('foo'));
192
+ assert(!copy.hasOwnProperty('bar'));
193
+ assert(!copy.hasOwnProperty('baz'));
194
+ }
195
+
196
+ { // overwriting a cloned object
197
+
198
+ const orig = {foo: {bar: 1}};
199
+ const bar3 = {bar: 3};
200
+
201
+ const copy = mutate/*:: <{foo: {bar: number}}, _>*/(orig, (copy) => {
202
+ copy.foo.bar = 2;
203
+ assert(copy.foo.bar === 2);
204
+ copy.foo = bar3;
205
+ assert(copy.foo.bar === 3);
206
+ copy.foo.bar = 4;
207
+ assert(copy.foo.bar === 4);
208
+ assert(bar3.bar === 3);
209
+ });
210
+
211
+ assert(orig.foo.bar === 1);
212
+ assert(copy.foo.bar === 4);
213
+ }
214
+
215
+ { // defineProperty
216
+
217
+ const orig/*: {+foo?: number, bar?: number} */ = {};
218
+
219
+ Object.defineProperty(orig, 'bar', {
220
+ configurable: false,
221
+ enumerable: true,
222
+ value: 0,
223
+ writable: false,
224
+ });
225
+
226
+ const copy = mutate/*:: <{foo?: number, bar?: number}, _>*/(orig, (copy) => {
227
+ let desc = Reflect.getOwnPropertyDescriptor(copy, 'bar');
228
+ // read-only properties are configurable and writable inside `mutate`
229
+ assert(desc && desc.configurable);
230
+ assert(desc && desc.writable);
231
+ copy.bar = 1;
232
+
233
+ // read-only properties defined inside `mutate` stay read-only
234
+ Object.defineProperty(copy, 'foo', {
235
+ configurable: false,
236
+ enumerable: true,
237
+ value: 1,
238
+ writable: false,
239
+ });
240
+
241
+ desc = Reflect.getOwnPropertyDescriptor(copy, 'foo');
242
+ let error;
243
+ try {
244
+ copy.foo = 2;
245
+ } catch (e) {
246
+ error = e;
247
+ }
248
+ assert(error && error.message.includes('read only property'));
249
+ });
250
+
251
+ assert(!orig.hasOwnProperty('foo'));
252
+ assert(orig.bar === 0);
253
+ assert(copy.bar === 1);
254
+ assert(Reflect.getOwnPropertyDescriptor(copy, 'bar').configurable === false);
255
+ assert(Reflect.getOwnPropertyDescriptor(copy, 'bar').writable === false);
256
+ assert(copy.foo === 1);
257
+ assert(Reflect.getOwnPropertyDescriptor(copy, 'foo').configurable === false);
258
+ assert(Reflect.getOwnPropertyDescriptor(copy, 'foo').writable === false);
259
+ }
260
+
261
+ { // array push
262
+
263
+ const copy = mutate/*:: <People, _>*/(people, (copy) => {
264
+ assert(Array.isArray(copy));
265
+ copy.push((alice/*: any */));
266
+ });
267
+
268
+ assert(copy !== people);
269
+ assert(people.length === 2);
270
+ assert(copy.length === 3);
271
+ assert(copy[2] === alice);
272
+ }
273
+
274
+ { // array delete
275
+
276
+ const copy = mutate/*:: <People, _>*/(people, (copy) => {
277
+ delete copy[1];
278
+ });
279
+
280
+ assert(copy !== people);
281
+ assert(copy.length === 2);
282
+ assert(copy[0] === alice);
283
+ assert(copy[1] === undefined);
284
+ }
285
+
286
+ { // splice
287
+ let copy = mutate/*:: <People, _>*/(people, (copy) => {
288
+ assert(Array.isArray(copy));
289
+ copy.splice(0, 1);
290
+ });
291
+
292
+ assert(copy !== people);
293
+ assert(people.length === 2);
294
+ assert(copy.length === 1);
295
+ assert(copy[0] === frozenBob);
296
+ }
297
+
298
+ { // objects are copied only once
299
+
300
+ const copy = mutate/*:: <People, _>*/(people, (copy) => {
301
+ const proxy1 = copy[0].death_date;
302
+ proxy1.year = 5000;
303
+ assert(proxy1 === copy[0].death_date); // proxy is unchanged
304
+ const ref1 = proxy1[PROXY_UNWRAP_KEY];
305
+ assert(people[0].death_date !== ref1); // reference was copied
306
+ assert(people[0].death_date.year === 2330);
307
+ assert(proxy1.year === 5000);
308
+ proxy1.year = 4000;
309
+ const ref2 = copy[0].death_date[PROXY_UNWRAP_KEY];
310
+ assert(ref1 === ref2); // reference was copied only once
311
+ assert(ref1.year === 4000);
312
+ proxy1.year = 3000;
313
+ });
314
+
315
+ assert(copy[0] !== alice);
316
+ assert(copy[0].birth_date === alice.birth_date);
317
+ assert(copy[0].death_date !== aliceDeathDate);
318
+ assert(copy[0].death_date.year === 3000);
319
+ assert(copy[1] === frozenBob);
320
+ }
321
+
322
+ /*::
323
+ type WeirdArray = {|+weird: boolean|} & $ReadOnlyArray<number>;
324
+ */
325
+
326
+ { // non-index array properties
327
+
328
+ const weird/*: WeirdArray */ = (Object.defineProperty([1, 2, 3], ('weird'/*: any */), {
329
+ configurable: false,
330
+ enumerable: true,
331
+ value: true,
332
+ writable: false,
333
+ })/*: any */);
334
+
335
+ const weirdCopy = mutate/*:: <{|weird: boolean|} & Array<number>, _>*/(weird, (copy) => {
336
+ copy.push(666);
337
+ assert(copy.weird === true, 'weird');
338
+ copy.weird = false;
339
+ });
340
+
341
+ assert(weird !== weirdCopy, 'reference is changed');
342
+ assert(weirdCopy.length === 4, 'item was added');
343
+ assert(weirdCopy[3], 'added item is 666');
344
+ assert(weird.length === 3, 'original reference is unchanged');
345
+ assert(weird.weird === true, 'original reference is unchanged');
346
+ assert(weirdCopy.weird === false, 'not weird');
347
+ }
348
+
349
+ { // array property descriptors
350
+
351
+ const descArray/*: {+value: Array<number>} */ = {value: [1, 2, 3]};
352
+
353
+ Object.defineProperty(descArray.value, 1, {
354
+ configurable: false,
355
+ enumerable: true,
356
+ value: 2,
357
+ writable: false,
358
+ });
359
+
360
+ const descArrayCopy = mutate/*:: <{value: Array<number>}, _>*/(descArray, (copy) => {
361
+ let desc = Object.getOwnPropertyDescriptor(copy.value, 'length');
362
+ assert(desc && desc.configurable === false);
363
+ assert(desc && desc.writable === true);
364
+ copy.value[1] = 0;
365
+ desc = Object.getOwnPropertyDescriptor(copy.value, 'length');
366
+ assert(desc && desc.configurable === false);
367
+ assert(desc && desc.writable === true);
368
+ });
369
+
370
+ assert(descArray.value[1] === 2);
371
+
372
+ const newDesc1 = Object.getOwnPropertyDescriptor(descArrayCopy.value, 1);
373
+ assert(!(newDesc1/*: any */).configurable);
374
+ assert((newDesc1/*: any */).enumerable);
375
+ assert((newDesc1/*: any */).value === 0);
376
+ assert(!(newDesc1/*: any */).writable);
377
+ }
378
+
379
+ /*::
380
+ type SharedFoo = {foo: string};
381
+ */
382
+
383
+ { // shared reference within one object
384
+
385
+ const shared/*: $ReadOnly<SharedFoo> */ = {foo: ''};
386
+
387
+ const object = {
388
+ prop1: shared,
389
+ prop2: shared,
390
+ };
391
+
392
+ const copy = mutate/*:: <{prop1: SharedFoo, prop2: SharedFoo}, _>*/(object, (copy) => {
393
+ copy.prop1.foo = 'abc';
394
+ assert(copy.prop1.foo === 'abc');
395
+ assert(copy.prop2.foo === '');
396
+ const ref = copy.prop1;
397
+ assert(shared.foo === '');
398
+ copy.prop2.foo = '123';
399
+ assert(copy.prop1.foo === 'abc');
400
+ assert(copy.prop2.foo === '123');
401
+ assert(copy.prop1[PROXY_UNWRAP_KEY] === ref[PROXY_UNWRAP_KEY]); // copied only once
402
+ assert(shared.foo === '');
403
+ });
404
+
405
+ assert(copy.prop1.foo === 'abc');
406
+ assert(copy.prop2.foo === '123');
407
+ assert(shared.foo === '');
408
+ }
409
+
410
+ /*::
411
+ type NestedShared = {foo: {foo: Array<number>}};
412
+ type ReadOnlyNestedShared = {+foo: {+foo: $ReadOnlyArray<number>}};
413
+ */
414
+
415
+ { // nested calls + shared reference across two objects
416
+
417
+ const shared/*: {+foo: $ReadOnlyArray<number>} */ = {foo: [1]};
418
+ const objectA/*: ReadOnlyNestedShared */ = {foo: shared};
419
+ const objectB/*: ReadOnlyNestedShared */ = {foo: shared};
420
+
421
+ const objectACopy = mutate/*:: <NestedShared, _>*/(objectA, (copyA) => {
422
+ const objectBCopy = mutate/*:: <NestedShared, _>*/(objectB, (copyB) => {
423
+ copyA.foo.foo[0] = 2;
424
+ copyB.foo.foo[0] = 3;
425
+ assert(copyA.foo.foo[0] === 2);
426
+ assert(copyB.foo.foo[0] === 3);
427
+ });
428
+ assert(copyA.foo.foo[0] === 2);
429
+ assert(objectBCopy.foo.foo[0] === 3);
430
+ });
431
+
432
+ assert(objectACopy.foo.foo[0] === 2);
433
+ }
434
+
435
+ { // nested call on proxied object
436
+
437
+ const source/*: {+foo: {+bar: number}} */ = {foo: {bar: 0}};
438
+
439
+ const copy = mutate/*:: <{foo: {bar: number}}, _>*/(source, copy => {
440
+ copy.foo.bar++;
441
+
442
+ copy.foo = mutate/*:: <{bar: number}, _>*/(copy.foo, fooCopy => {
443
+ copy.foo.bar++; // overridden
444
+ fooCopy.bar++;
445
+ copy.foo.bar++; // overridden
446
+ });
447
+
448
+ copy.foo.bar++;
449
+ });
450
+
451
+ assert(copy.foo.bar === 3);
452
+ }
453
+
454
+ { // classes
455
+
456
+ class NiceBase {}
457
+
458
+ class NiceClass extends NiceBase {
459
+ /*::
460
+ value: string;
461
+ */
462
+
463
+ constructor() {
464
+ super();
465
+ this.value = 'nice';
466
+ }
467
+ }
468
+
469
+ const instance = new NiceClass();
470
+
471
+ const copy = mutate/*:: <NiceClass, _>*/(instance, (copy) => {
472
+ assert(copy instanceof NiceClass);
473
+ copy.value = 'naughty';
474
+ });
475
+
476
+ assert(instance !== copy);
477
+ assert(copy instanceof NiceClass);
478
+ assert(copy.value === 'naughty');
479
+ assert(instance.value === 'nice');
480
+ }
481
+
482
+ { // getters and setters
483
+
484
+ const orig = {
485
+ object: null,
486
+ get value()/*: {foo?: number} */{
487
+ return (this.object = this.object || {});
488
+ },
489
+ set value(x/*: {foo?: number} */)/*: void */{
490
+ this.object = x;
491
+ },
492
+ };
493
+
494
+ const copy1 = mutate/*:: <typeof orig, _>*/(orig, (copy) => {
495
+ copy.value.foo = 1;
496
+ });
497
+
498
+ const newObject = {foo: 0};
499
+
500
+ const copy2 = mutate/*:: <typeof orig, _>*/(orig, (copy) => {
501
+ copy.value = newObject;
502
+ assert(copy.value.foo === 0);
503
+ // This should copy newObject; otherwise we'd be able to have
504
+ // side-effects across multiple objects.
505
+ copy.value.foo = 2;
506
+ assert(newObject.foo === 0);
507
+ });
508
+
509
+ assert(orig.value.foo === undefined);
510
+ assert(orig.value === orig.object);
511
+ assert(copy1.value.foo === 1);
512
+ assert(copy1.value === copy1.object);
513
+ assert(copy2.value.foo === 2);
514
+ assert(copy2.value === copy2.object);
515
+ }
516
+
517
+ { // functions
518
+
519
+ const origFunc = function () {
520
+ return this.value;
521
+ };
522
+
523
+ const orig/*: {|+func: () => string, +value: string|} */ = {
524
+ func: origFunc,
525
+ value: 'abc',
526
+ };
527
+
528
+ const copy = mutate/*:: <{func: (() => string) & {prop?: boolean}, value: string}, _>*/(orig, (copy) => {
529
+ assert(typeof copy.func === 'function');
530
+ assert(copy.func instanceof Function);
531
+ assert(copy.func() === 'abc');
532
+ copy.value = '123';
533
+ assert(copy.func() === '123');
534
+
535
+ let error;
536
+ try {
537
+ copy.func.prop = true;
538
+ } catch (e) {
539
+ error = e;
540
+ }
541
+
542
+ // Can't clone functions, so the above should error.
543
+ assert(error && error.message.includes('unsupported'));
544
+
545
+ copy.func = () => '';
546
+ assert(copy.func() === '');
547
+ });
548
+ }
549
+
550
+ { // null prorotypes
551
+
552
+ const orig/*: {__proto__: null, +value: {__proto__: null, +number: number}} */ = Object.create(null, {
553
+ value: {
554
+ configurable: true,
555
+ enumerable: true,
556
+ value: Object.create(null, {
557
+ number: {
558
+ configurable: true,
559
+ enumerable: true,
560
+ value: 1,
561
+ writable: false,
562
+ },
563
+ }),
564
+ writable: false,
565
+ },
566
+ });
567
+
568
+ const copy = mutate/*:: <{__proto__: null, value: {__proto__: null, number: number}}, _>*/(orig, (copy) => {
569
+ copy.value.number = 2;
570
+ });
571
+
572
+ assert(orig.value.number === 1);
573
+ assert(copy.value.number === 2);
574
+ assert(Object.getPrototypeOf(copy) === null);
575
+ assert(Object.getPrototypeOf(copy.value) === null);
576
+ }
577
+
578
+ { // Number / String objects
579
+
580
+ const orig/*: {+num: Number, +str: String} */ = {
581
+ num: new Number(1),
582
+ str: new String('hello'),
583
+ };
584
+
585
+ const copy = mutate/*:: <{num: Number, str: String}, _>*/(orig, (copy) => {
586
+ let error;
587
+
588
+ error = null;
589
+ try {
590
+ copy.num.valueOf();
591
+ } catch (e) {
592
+ error = e;
593
+ }
594
+ assert(error && error.message.includes('unsupported'));
595
+
596
+ copy.num = new Number(orig.num.valueOf() + 2);
597
+
598
+ error = null;
599
+ try {
600
+ copy.str[4] = 'p';
601
+ } catch (e) {
602
+ error = e;
603
+ }
604
+ assert(error && error.message.includes('unsupported'));
605
+ });
606
+
607
+ assert(orig.num.valueOf() === 1);
608
+ assert(orig.str.valueOf() === 'hello');
609
+ assert(copy.num.valueOf() === 3);
610
+ assert(copy.str === orig.str);
611
+ }
612
+
613
+ { // non-objects
614
+
615
+ let error = null;
616
+ try { mutate(null, () => {}) } catch (e) { error = e }
617
+ assert(error && error.message === 'Expected an object to mutate');
618
+ }
619
+
620
+ { // derived built-ins
621
+
622
+ class FunDate extends Date {}
623
+
624
+ let error = null;
625
+ try {
626
+ mutate(new FunDate(), (copy) => {
627
+ copy.setFullYear(1999);
628
+ });
629
+ } catch (e) {
630
+ error = e;
631
+ }
632
+ assert(error && error.message.includes('unsupported'));
633
+ }
634
+
635
+ /*::
636
+ type Cyclic = {x: Cyclic}
637
+ */
638
+
639
+ { // cyclic references
640
+
641
+ const object/*: Cyclic */ = {};
642
+ object.x = object;
643
+
644
+ const newObject = mutate(object, newObject => {
645
+ newObject.x.x = null;
646
+ });
647
+
648
+ assert(object.x.x.x === object);
649
+ assert(newObject.x.x === null);
650
+ }
651
+
652
+ { // using a copy reference in an object spread
653
+
654
+ const orig/*: any */ = {
655
+ foo: {
656
+ bar: {
657
+ baz: 1,
658
+ },
659
+ func: function () {
660
+ return this;
661
+ },
662
+ },
663
+ };
664
+
665
+ let copy = mutate/*:: <any, _>*/(orig, (copy) => {
666
+ copy.foo = {...copy.foo};
667
+ });
668
+
669
+ let error = null;
670
+ try {
671
+ copy.foo.bar.baz;
672
+ } catch (e) {
673
+ error = e;
674
+ }
675
+ assert(error && error.message.includes('forgotten to call unwrap'));
676
+
677
+ // Check that the error can trigger twice
678
+ try {
679
+ copy.foo.bar.baz;
680
+ } catch (e) {
681
+ error = e;
682
+ }
683
+ assert(error && error.message.includes('forgotten to call unwrap'));
684
+
685
+ copy = mutate/*:: <any, _>*/(orig, (copy, unwrap) => {
686
+ copy.foo = {...unwrap(copy.foo)};
687
+ assert(copy.foo.func() === copy.foo);
688
+ });
689
+
690
+ assert(copy.foo.func() === copy.foo);
691
+ assert(copy.foo.bar === orig.foo.bar);
692
+ }
693
+
694
+ { // construct
695
+
696
+ function Cls() {
697
+ this.foo = 1;
698
+ }
699
+
700
+ const orig = {
701
+ Cls,
702
+ };
703
+
704
+ const copy = mutate(orig, (copy) => {
705
+ const c = new copy.Cls();
706
+ assert(c instanceof Cls);
707
+ assert(c.foo === 1);
708
+ });
709
+ }
710
+
711
+ { // Object.freeze on a proxied object
712
+
713
+ // Arrays are somewhat special, because Object.freeze will attempt to make
714
+ // the `length` property non-configurable and non-writable via the
715
+ // defineProperty trap. However, a non-configurable property "cannot be
716
+ // non-writable, unless there exists a corresponding non-configurable,
717
+ // non-writable own property on the target object," i.e. our fake proxy
718
+ // target.
719
+ const copy = mutate([0], copy => {
720
+ Object.freeze(copy);
721
+
722
+ const desc = Object.getOwnPropertyDescriptor(copy, 'length');
723
+ assert(desc && !desc.configurable);
724
+ assert(desc && !desc.writable);
725
+
726
+ let error;
727
+ try {
728
+ copy.error = true;
729
+ } catch (e) {
730
+ error = e;
731
+ }
732
+ assert(error && error.message.includes('not extensible'));
733
+ assert(Object.isExtensible(copy) === false);
734
+ });
735
+ assert(Object.isExtensible(copy) === false);
736
+ }
737
+
738
+ { // Assigning an externally-frozen object inside the proxy
739
+
740
+ const source = {ref: null};
741
+ const frozenRef = {name: ''};
742
+ Object.freeze(frozenRef);
743
+
744
+ const copy = mutate/*:: <{ref: {name: string}}, _>*/(source, copy => {
745
+ copy.ref = frozenRef;
746
+ copy.ref.name = 'hi';
747
+ });
748
+ assert(copy.ref && copy.ref.name === 'hi');
749
+ }
@@ -5,8 +5,8 @@
5
5
  * in the file named "LICENSE" at the root directory of this distribution.
6
6
  */
7
7
 
8
- import {PROXY_UNWRAP_KEY} from './constants';
9
- import isObject from './isObject';
8
+ import {PROXY_UNWRAP_KEY} from './constants.mjs';
9
+ import isObject from './isObject.mjs';
10
10
 
11
11
  export default function unwrap(value) {
12
12
  if (isObject(value)) {
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('esm')(module)('./main.js');