diff --git a/README.md b/README.md index ae4cae02..72de3f7b 100644 --- a/README.md +++ b/README.md @@ -110,23 +110,23 @@ Mutative is up to 6x faster than naive handcrafted reducer for updating immutabl > Mutative passed all of Immer's test cases. -Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.9 vs Immer v10.1.1] +Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.10 vs Immer v10.1.1] ![Benchmark](benchmark.jpg) ``` -Naive handcrafted reducer - No Freeze x 4,447 ops/sec ±0.85% (96 runs sampled) -Mutative - No Freeze x 6,246 ops/sec ±1.29% (92 runs sampled) -Immer - No Freeze x 5.26 ops/sec ±0.56% (18 runs sampled) +Naive handcrafted reducer - No Freeze x 4,439 ops/sec ±0.65% (98 runs sampled) +Mutative - No Freeze x 6,300 ops/sec ±1.19% (94 runs sampled) +Immer - No Freeze x 5.26 ops/sec ±0.59% (18 runs sampled) -Mutative - Freeze x 950 ops/sec ±0.95% (96 runs sampled) -Immer - Freeze x 377 ops/sec ±0.37% (93 runs sampled) +Mutative - Freeze x 937 ops/sec ±1.25% (95 runs sampled) +Immer - Freeze x 378 ops/sec ±0.66% (93 runs sampled) -Mutative - Patches and No Freeze x 978 ops/sec ±0.16% (97 runs sampled) -Immer - Patches and No Freeze x 5.23 ops/sec ±0.25% (18 runs sampled) +Mutative - Patches and No Freeze x 975 ops/sec ±0.17% (99 runs sampled) +Immer - Patches and No Freeze x 5.29 ops/sec ±0.30% (18 runs sampled) -Mutative - Patches and Freeze x 504 ops/sec ±1.01% (94 runs sampled) -Immer - Patches and Freeze x 272 ops/sec ±0.83% (89 runs sampled) +Mutative - Patches and Freeze x 512 ops/sec ±0.85% (98 runs sampled) +Immer - Patches and Freeze x 278 ops/sec ±0.57% (90 runs sampled) The fastest method is Mutative - No Freeze ``` @@ -137,7 +137,7 @@ Run `yarn benchmark` to measure performance. Immer relies on auto-freeze to be enabled, if auto-freeze is disabled, Immer will have a huge performance drop and Mutative will have a huge performance lead, especially with large data structures it will have a performance lead of more than 50x. -So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,246 ops/sec`) and Immer (`377 ops/sec`). +So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,300 ops/sec`) and Immer (`378 ops/sec`). Overall, Mutative has a huge performance lead over Immer in [more performance testing scenarios](https://github.com/unadlib/mutative/tree/main/test/performance). Run `yarn performance` to get all the performance results locally. diff --git a/benchmark.jpg b/benchmark.jpg index d61a37b5..9bcb4b87 100644 Binary files a/benchmark.jpg and b/benchmark.jpg differ diff --git a/src/apply.ts b/src/apply.ts index 06dc5393..b358c3f9 100644 --- a/src/apply.ts +++ b/src/apply.ts @@ -67,10 +67,7 @@ export function apply( ); } // use `index` in Set draft - base = get( - getType(base) === DraftType.Set ? Array.from(base) : base, - key - ); + base = get(parentType === DraftType.Set ? Array.from(base) : base, key); if (typeof base !== 'object') { throw new Error(`Cannot apply patch at '${path.join('/')}'.`); } diff --git a/src/utils/draft.ts b/src/utils/draft.ts index 565ce35a..c05574fc 100644 --- a/src/utils/draft.ts +++ b/src/utils/draft.ts @@ -58,7 +58,15 @@ export function getPath( if (target.parent) { return getPath(target.parent, path); } - return path.reverse(); + // `target` is root draft. + path.reverse(); + try { + // check if the path is valid + resolvePath(target.copy, path); + } catch (e) { + return null; + } + return path; } export function getType(target: any) { @@ -124,3 +132,15 @@ export function unescapePath(path: string | (string | number)[]) { .map((_item) => _item.replace(/~1/g, '/').replace(/~0/g, '~')) .slice(1); } + +export function resolvePath(base: any, path: (string | number)[]) { + for (let index = 0; index < path.length - 1; index += 1) { + const key = path[index]; + // use `index` in Set draft + base = get(getType(base) === DraftType.Set ? Array.from(base) : base, key); + if (typeof base !== 'object') { + throw new Error(`Cannot resolve patch at '${path.join('/')}'.`); + } + } + return base; +} diff --git a/test/index.test.ts b/test/index.test.ts index 23f42bc0..5bdcfe6c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -3992,3 +3992,94 @@ test('#57 - curried create returns object of wrong type', () => { const nextState = finalize(); expect(nextState).toEqual({ a: { x: 'test' } }); }); + +test('#59 - Failure to apply inverse patchset', () => { + const parentId = 'parent'; + const childId = 'child'; + const myObj = { + [parentId]: { name: 'parent', children: [childId] }, + [childId]: { name: 'child' }, + }; + const [newState, patchset, inverse] = create( + myObj, + (draft: any) => { + // delete children + while (draft[parentId].children.length) { + const id = draft[parentId].children[0]; + draft[parentId].children.splice(0, 1); + delete draft[id]; // delete child object + } + + // delete parent + // @ts-ignore + delete draft[parentId]; + }, + { enablePatches: true } + ); + const reverted = apply(newState, inverse); + expect(reverted).toEqual(myObj); + const reverted2 = apply(myObj, patchset); + expect(reverted2).toEqual(newState); +}); + +test('#59 - Failure to apply inverse patchset(Set)', () => { + const parentId = 'parent'; + const childId = 'child'; + + const myObj = { + [parentId]: new Set([{ name: 'parent', children: [childId] }]), + [childId]: { name: 'child' }, + }; + + const [newState, patchset, inverse] = create( + myObj, + (draft: any) => { + // delete children + const parent: any = Array.from(draft[parentId])[0]; + while (parent.children.length) { + const id = parent.children[0]; + parent.children.splice(0, 1); + delete draft[id]; // delete child object + } + + // delete parent + draft[parentId].clear(); + }, + { enablePatches: true } + ); + const reverted = apply(newState, inverse); + expect(reverted).toEqual(myObj); + const reverted2 = apply(myObj, patchset); + expect(reverted2).toEqual(newState); +}); + +test('#59 - Failure to apply inverse patchset(Map)', () => { + const parentId = 'parent'; + const childId = 'child'; + + const myObj = { + [parentId]: new Map([[0, { name: 'parent', children: [childId] }]]), + [childId]: { name: 'child' }, + }; + + const [newState, patchset, inverse] = create( + myObj, + (draft: any) => { + // delete children + const parent: any = draft[parentId].get(0); + while (parent.children.length) { + const id = parent.children[0]; + parent.children.splice(0, 1); + delete draft[id]; // delete child object + } + + // delete parent + draft[parentId].clear(); + }, + { enablePatches: true } + ); + const reverted = apply(newState, inverse); + expect(reverted).toEqual(myObj); + const reverted2 = apply(myObj, patchset); + expect(reverted2).toEqual(newState); +}); diff --git a/website/blog/releases/1.0/index.md b/website/blog/releases/1.0/index.md index 6fc0bf8e..032249d6 100644 --- a/website/blog/releases/1.0/index.md +++ b/website/blog/releases/1.0/index.md @@ -81,23 +81,23 @@ const state = create(baseState, (draft) => { > Mutative passed all of Immer's test cases. -Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.9 vs Immer v10.1.1] +Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.10 vs Immer v10.1.1] ![Benchmark](img/benchmark.jpg) ``` -Naive handcrafted reducer - No Freeze x 4,447 ops/sec ±0.85% (96 runs sampled) -Mutative - No Freeze x 6,246 ops/sec ±1.29% (92 runs sampled) -Immer - No Freeze x 5.26 ops/sec ±0.56% (18 runs sampled) +Naive handcrafted reducer - No Freeze x 4,439 ops/sec ±0.65% (98 runs sampled) +Mutative - No Freeze x 6,300 ops/sec ±1.19% (94 runs sampled) +Immer - No Freeze x 5.26 ops/sec ±0.59% (18 runs sampled) -Mutative - Freeze x 950 ops/sec ±0.95% (96 runs sampled) -Immer - Freeze x 377 ops/sec ±0.37% (93 runs sampled) +Mutative - Freeze x 937 ops/sec ±1.25% (95 runs sampled) +Immer - Freeze x 378 ops/sec ±0.66% (93 runs sampled) -Mutative - Patches and No Freeze x 978 ops/sec ±0.16% (97 runs sampled) -Immer - Patches and No Freeze x 5.23 ops/sec ±0.25% (18 runs sampled) +Mutative - Patches and No Freeze x 975 ops/sec ±0.17% (99 runs sampled) +Immer - Patches and No Freeze x 5.29 ops/sec ±0.30% (18 runs sampled) -Mutative - Patches and Freeze x 504 ops/sec ±1.01% (94 runs sampled) -Immer - Patches and Freeze x 272 ops/sec ±0.83% (89 runs sampled) +Mutative - Patches and Freeze x 512 ops/sec ±0.85% (98 runs sampled) +Immer - Patches and Freeze x 278 ops/sec ±0.57% (90 runs sampled) The fastest method is Mutative - No Freeze ``` @@ -108,7 +108,7 @@ Run `yarn benchmark` to measure performance. Immer relies on auto-freeze to be enabled, if auto-freeze is disabled, Immer will have a huge performance drop and Mutative will have a huge performance lead, especially with large data structures it will have a performance lead of more than 50x. -So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,246 ops/sec`) and Immer (`377 ops/sec`). +So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,300 ops/sec`) and Immer (`378 ops/sec`). Overall, Mutative has a huge performance lead over Immer in [more performance testing scenarios](https://github.com/unadlib/mutative/tree/main/test/performance). diff --git a/website/docs/extra-topics/comparison-with-immer.md b/website/docs/extra-topics/comparison-with-immer.md index 98b90454..a17041db 100644 --- a/website/docs/extra-topics/comparison-with-immer.md +++ b/website/docs/extra-topics/comparison-with-immer.md @@ -25,23 +25,23 @@ Mutative has fewer bugs such as accidental draft escapes than Immer, [view detai > Mutative passed all of Immer's test cases. -Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.9 vs Immer v10.1.1] +Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.10 vs Immer v10.1.1] ![Benchmark](img/benchmark.jpg) ``` -Naive handcrafted reducer - No Freeze x 4,447 ops/sec ±0.85% (96 runs sampled) -Mutative - No Freeze x 6,246 ops/sec ±1.29% (92 runs sampled) -Immer - No Freeze x 5.26 ops/sec ±0.56% (18 runs sampled) +Naive handcrafted reducer - No Freeze x 4,439 ops/sec ±0.65% (98 runs sampled) +Mutative - No Freeze x 6,300 ops/sec ±1.19% (94 runs sampled) +Immer - No Freeze x 5.26 ops/sec ±0.59% (18 runs sampled) -Mutative - Freeze x 950 ops/sec ±0.95% (96 runs sampled) -Immer - Freeze x 377 ops/sec ±0.37% (93 runs sampled) +Mutative - Freeze x 937 ops/sec ±1.25% (95 runs sampled) +Immer - Freeze x 378 ops/sec ±0.66% (93 runs sampled) -Mutative - Patches and No Freeze x 978 ops/sec ±0.16% (97 runs sampled) -Immer - Patches and No Freeze x 5.23 ops/sec ±0.25% (18 runs sampled) +Mutative - Patches and No Freeze x 975 ops/sec ±0.17% (99 runs sampled) +Immer - Patches and No Freeze x 5.29 ops/sec ±0.30% (18 runs sampled) -Mutative - Patches and Freeze x 504 ops/sec ±1.01% (94 runs sampled) -Immer - Patches and Freeze x 272 ops/sec ±0.83% (89 runs sampled) +Mutative - Patches and Freeze x 512 ops/sec ±0.85% (98 runs sampled) +Immer - Patches and Freeze x 278 ops/sec ±0.57% (90 runs sampled) The fastest method is Mutative - No Freeze ``` @@ -52,7 +52,7 @@ Run `yarn benchmark` to measure performance. Immer relies on auto-freeze to be enabled, if auto-freeze is disabled, Immer will have a huge performance drop and Mutative will have a huge performance lead, especially with large data structures it will have a performance lead of more than 50x. -So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,246 ops/sec`) and Immer (`377 ops/sec`). +So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,300 ops/sec`) and Immer (`378 ops/sec`). Overall, Mutative has a huge performance lead over Immer in [more performance testing scenarios](https://github.com/unadlib/mutative/tree/main/test/performance). Run `yarn performance` to get all the performance results locally. diff --git a/website/docs/getting-started/performance.md b/website/docs/getting-started/performance.md index 6ca9f958..434a288a 100644 --- a/website/docs/getting-started/performance.md +++ b/website/docs/getting-started/performance.md @@ -69,23 +69,23 @@ const state = create(baseState, (draft) => { > Mutative passed all of Immer's test cases. -Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.9 vs Immer v10.1.1] +Measure(ops/sec) to update 50K arrays and 1K objects, bigger is better([view source](https://github.com/unadlib/mutative/blob/main/test/performance/benchmark.ts)). [Mutative v1.0.10 vs Immer v10.1.1] ![Benchmark](img/benchmark.jpg) ``` -Naive handcrafted reducer - No Freeze x 4,447 ops/sec ±0.85% (96 runs sampled) -Mutative - No Freeze x 6,246 ops/sec ±1.29% (92 runs sampled) -Immer - No Freeze x 5.26 ops/sec ±0.56% (18 runs sampled) +Naive handcrafted reducer - No Freeze x 4,439 ops/sec ±0.65% (98 runs sampled) +Mutative - No Freeze x 6,300 ops/sec ±1.19% (94 runs sampled) +Immer - No Freeze x 5.26 ops/sec ±0.59% (18 runs sampled) -Mutative - Freeze x 950 ops/sec ±0.95% (96 runs sampled) -Immer - Freeze x 377 ops/sec ±0.37% (93 runs sampled) +Mutative - Freeze x 937 ops/sec ±1.25% (95 runs sampled) +Immer - Freeze x 378 ops/sec ±0.66% (93 runs sampled) -Mutative - Patches and No Freeze x 978 ops/sec ±0.16% (97 runs sampled) -Immer - Patches and No Freeze x 5.23 ops/sec ±0.25% (18 runs sampled) +Mutative - Patches and No Freeze x 975 ops/sec ±0.17% (99 runs sampled) +Immer - Patches and No Freeze x 5.29 ops/sec ±0.30% (18 runs sampled) -Mutative - Patches and Freeze x 504 ops/sec ±1.01% (94 runs sampled) -Immer - Patches and Freeze x 272 ops/sec ±0.83% (89 runs sampled) +Mutative - Patches and Freeze x 512 ops/sec ±0.85% (98 runs sampled) +Immer - Patches and Freeze x 278 ops/sec ±0.57% (90 runs sampled) The fastest method is Mutative - No Freeze ``` @@ -96,7 +96,7 @@ Run `yarn benchmark` to measure performance. Immer relies on auto-freeze to be enabled, if auto-freeze is disabled, Immer will have a huge performance drop and Mutative will have a huge performance lead, especially with large data structures it will have a performance lead of more than 50x. -So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,246 ops/sec`) and Immer (`377 ops/sec`). +So if you are using Immer, you will have to enable auto-freeze for performance. Mutative is disabled auto-freeze by default. With the default configuration of both, we can see the 16x performance gap between Mutative (`6,300 ops/sec`) and Immer (`378 ops/sec`). Overall, Mutative has a huge performance lead over Immer in [more performance testing scenarios](https://github.com/unadlib/mutative/tree/main/test/performance). Run `yarn performance` to get all the performance results locally.