Mutative: Efficient immutable updates, more than 10x faster than Immer

https://github.com/unadlib/mutative

Mutative Logo

Node CI Coverage Status npm NPM Downloads license

Mutative - A JavaScript library for efficient immutable updates. Across 90 benchmarked workloads, it is about 3x faster than Immer with the same settings and 6x faster out of the box.

The gap widens on large arrays, where Mutative moves elements up to 1,125x faster than Immer. When copying dominates, such as updating objects with thousands of keys or inserting at the front of a large array, Mutative is even faster than hand-written spread reducers.

How does Mutative compare with the spread operation (hand-written reducers)?

Mutative copies each object at most once per update, however many changes a recipe makes, and focuses on shallow copy optimization, more complete lazy drafts, finalization process optimization, and more.

Motivation

Writing immutable updates by hand is usually difficult, prone to errors, and cumbersome. Immer helps us write simpler immutable updates with "mutative" logic.

But its performance issue causes a runtime performance overhead. Immer enables auto-freeze by default, and such frozen immutable state is not common. In scenarios such as cross-processing, remote data transfer, etc., these immutable data must be constantly frozen.

There are more parts that could be improved, such as better type inference, non-intrusive markup, support for more types of immutability, Safer immutability, more edge cases, and so on.

This is why Mutative was created.

Performance

Mutative passed all of Immer's test cases.

The benchmark suite times 93 workloads: Immer's own performance tests, array methods, reads, Map and Set values, object records, class instances, a deep path, patch application, returned values, and searches. It compares Mutative with Immer 11.1.18 and with reducers written by hand, after checking every result against those reducers. The performance summary has the complete results, the method, and their limits.

With matched settings, both freezing or both not and both generating patches or both not, Mutative was faster than Immer in 508 of 530 measured cases, 3.1x on geometric mean. With each library's defaults, Mutative without auto-freeze and Immer with it, Mutative was faster in 133 of 136 cases, 6.0x on geometric mean.

Times are microseconds per update, medians of three runs on an Apple M1 Max with Node.js 24.16.0; lower is better. Mutative, the first Immer column, and the hand-written reducers run without auto-freeze; the second Immer column shows Immer's default.

Workload Rows Mutative Immer Immer, auto-freeze Hand-written
Update one field of a small object — 0.65 1.02 1.32 0.05
Update an array item found by ID 100 1.33 1.99 25.9 0.84
200 RTK Query-style updates — 1,031 1,180 5,209 361
Read every row by index 10,000 5,210 11,253 11,513 135
Remove the first row 10,000 7.37 8,292 9,125 1.43
Update every row 10,000 7,008 12,803 16,454 243
Update one of 10,000 records 10,000 1,242 2,172 3,222 2,177
Insert into a 1,000-property object — 53.0 151 238 131
Update a Map value 10,000 553 557 774 548
Add a number to a Set 10,000 1,013 1,502 1,605 72.4
Update a class instance 10,000 3.19 3.74 673 1.49
Update a value ten levels deep — 3.70 4.75 8.27 0.87
Apply patches to 10% of rows 10,000 1,558 1,465 2,440 —
Return draft.filter() 10,000 2,444 4,188 4,645 80.2
Return a new state 10,000 2,914 8,272 0.89 0.06
Return it with rawReturn() 10,000 0.30 7,865 0.89 0.06

The record and 1,000-property rows ran each library in a process of its own, because code that ran earlier in a process changes how fast V8 copies such wide objects. Immer has no rawReturn() and returns the same plain value in the last two rows. Immer was faster in 11 of the 530 matched cases: when returning a new state built from frozen data, which Immer does not search for drafts; when applying patches that replace nested values, by 5-7%; and, with auto-freeze, when updating a class instance with 1,000 fields.

Run pnpm benchmark:immer to measure the suite; the benchmark guide describes its options.

Large arrays

At 1,000 and 10,000 rows the gap grows. Against Immer without its array-method plugin, Mutative was faster in 164 of 176 cases at these sizes, 3.5x on geometric mean, and faster in every case that moves elements:

Auto-freeze Patches All workloads, 1,000 rows All workloads, 10,000 rows Moves, 1,000 rows Moves, 10,000 rows
off off 7.3x 8.8x 212x 472x
off on 3.9x 4.2x 6.7x 7.7x
on off 2.6x 2.3x 32x 35x
on on 1.9x 1.7x 6.2x 6.8x

Each value is the geometric mean of Immer's time over Mutative's; moves are shift, unshift, splice insertion, and reverse. Mutative moves elements natively on its copy, while Immer moves each one through its draft proxy: removing the first of 10,000 rows took 7.37 µs against 8,292 µs, 1,125x. With patches, both libraries emit one patch per moved index, which bounds the gain to 6-8x. The performance summary breaks these results down by scenario.

With patches

With patches on and auto-freeze off, Mutative was faster than Immer in 126 of 129 cases and never slower, 3.1x on geometric mean, and faster than Mutative 1.3.0 in 103 and within 5% in the rest, 3.7x. In every array case measured it was faster than both: 4.0x Immer and 13x Mutative 1.3.0 on geometric mean. Patches cost little when an update changes a few paths: pushing a row and inserting a property at 10,000 rows took 65.2 µs with patches against 64.8 µs without. Moving elements emits one patch per moved index in every library, so removing the first of 10,000 rows produces 10,000 forward and 10,000 inverse patches.

Times are microseconds per update with patches on and auto-freeze off:

Workload Rows Mutative Immer Mutative 1.3.0
Push a row and insert a property 10,000 65.2 370 212
Update an array item found by ID 100 2.17 3.57 3.85
200 RTK Query-style updates — 1,325 1,520 2,172
Remove the first row with splice 100 13.5 86.5 3,108
Insert a row in the middle 100 9.91 51.3 1,297
Sort rows 100 108 217 2,622
Remove the first row with shift 10,000 1,227 10,117 282,588
Reverse the rows 10,000 1,226 10,167 286,207
Update every row 10,000 11,591 22,553 21,851
Update one of 10,000 records 10,000 1,236 2,165 1,230

Immer's optional enableArrayMethods() plugin also runs array methods on the draft's copy, and with patches it comes within 1.05-1.74x of Mutative when moving elements of 1,000 or 10,000 rows. These comparisons leave it off because in Immer 11.1.18 it breaks guarantees that Immer otherwise keeps: shift, pop and splice return raw base objects, so editing a removed object changes the previous state; reordering can expose original objects the same way and leave revoked drafts in the result; and its forward patches can fail to replay. In the audit, 123 of 4,913 three-step operation sequences break with the plugin and none without it. The performance summary reports Immer with the plugin separately.

Bundle size

Mutative ships patches, Map/Set support and native array methods built in; Immer provides them as opt-in plugins. The following Brotli sizes were measured with esbuild 0.24.0 from the ESM entry that bundlers resolve for each library (Immer 11.1.21 dist/immer.mjs; Mutative dist/mutative.esm.mjs at source 18a0ea7), with process.env.NODE_ENV defined as production, bundled for the browser with --minify --target=es2018 --format=esm. The first row imports only produce or create. The second adds applyPatches, current and original with Immer's enablePatches, enableMapSet and enableArrayMethods, and apply, current and original for Mutative.

Bundle Immer Mutative
produce / create only 3.6 kB 7.4 kB
With patches, Map/Set and array methods 6.4 kB 7.9 kB

Mutative's create includes patches, Map/Set support and the native array methods even when a recipe does not use them: they are part of create, not separate imports, so bundlers cannot drop them. The difference buys the draft fast paths and the native array methods measured in the performance summary, which also records the artifact sizes of each measured source. See the array methods FAQ for the supported fast paths and their contract, and the Immer regression cases for the behavior of its array-method plugin.

Features and Benefits

  • Mutation makes immutable updates - Immutable data structures supporting objects, arrays, Sets and Maps.
  • High performance - About 6x faster than Immer out of the box, and faster than hand-written spreads when updating objects with thousands of keys or inserting at the front of large arrays.
  • Optional freezing state - No freezing of immutable data by default.
  • Support for JSON Patch - Full compliance with JSON Patch specification.
  • Custom shallow copy - Support for more types of immutable data.
  • Support mark for immutable and mutable data - Allows for non-invasive marking.
  • Safer mutable data access in strict mode - It brings more secure immutable updates.
  • Support for reducer - Support reducer function and any other immutable state library.

Difference between Mutative and Immer

Mutative Immer
Custom shallow copy ✅ ❌
Strict mode ✅ ❌
No data freeze by default ✅ ❌
Non-invasive marking ✅ ❌
Complete freeze data ✅ ❌
Non-global config ✅ ❌
async draft function ✅ ❌
Fully compatible with JSON Patch spec ✅ ❌
new Set methods(Mutative v1.1.0+) ✅ ❌

Mutative has fewer bugs such as accidental draft escapes than Immer, view details.

Installation

Yarn

NPM

CDN

  • Unpkg: <script src="https://unpkg.com/mutative"></script>
  • JSDelivr: <script src="https://cdn.jsdelivr.net/npm/mutative"></script>
  • ES module: import { create } from 'https://unpkg.com/mutative/dist/mutative.esm.production.min.mjs';

The package's other ESM files read process.env.NODE_ENV, which bundlers and Node.js provide; a browser without a bundler needs the production file above.

Usage

import { create } from "mutative";
const baseState = {
  foo: "bar",
  list: [{ text: "coding" }],
};
const state = create(baseState, (draft) => {
  draft.list.push({ text: "learning" });
});
expect(state).not.toBe(baseState);
expect(state.list).not.toBe(baseState.list);

create(baseState, (draft) => void, options?: Options): newState

The first argument of create() is the base state. Mutative drafts it and passes it to the arguments of the draft function, and performs the draft mutation until the draft function finishes, then Mutative will finalize it and produce the new state.

Use create() for more advanced features by setting options.

APIs

create()

Use create() for draft mutation to get a new state, which also supports currying.

import { create } from "mutative";
const baseState = {
  foo: "bar",
  list: [{ text: "todo" }],
};
const state = create(baseState, (draft) => {
  draft.foo = "foobar";
  draft.list.push({ text: "learning" });
});

In this basic example, the changes to the draft are 'mutative' within the draft callback, and create() is finally executed with a new immutable state.

create(state, fn, options)

Then options is optional.

  • strict - boolean, the default is false.

    Forbid accessing non-draftable values in strict mode(unless using unsafe()).

    When strict mode is enabled, mutable data can only be accessed using unsafe().

    It is recommended to enable strict in development mode and disable strict in production mode. This will ensure safe explicit returns and also keep good performance in the production build. If the value that does not mix any current draft or is undefined is returned, then use rawReturn().

    If you'd like to enable strict mode by default in a development build and turn it off for production, you can use strict: process.env.NODE_ENV !== 'production'.

    In development builds, strict mode also warns once when a recipe leaves 1,000 or more drafts unchanged, as a search through a large draft array does. See current() for searching without creating drafts.

  • enablePatches - boolean | { pathAsArray?: boolean; arrayLengthAssignment?: boolean; }, the default is false.

    Enable patch, and return the patches/inversePatches.

    If you need to set the shape of the generated patch in more detail, then you can set pathAsArray and arrayLengthAssignment。pathAsArray default value is true, if it's true, the path will be an array, otherwise it is a string; arrayLengthAssignment default value is true, if it's true, the array length will be included in the patches, otherwise no include array length(NOTE: If arrayLengthAssignment is false, it is fully compatible with JSON Patch spec, but it may have additional performance loss), view related discussions.

  • enableAutoFreeze - boolean, the default is false.

    Enable autoFreeze, and return frozen state, and enable circular reference checking only in development mode.

  • mark - (target) => ('mutable'|'immutable'|function) | (target) => ('mutable'|'immutable'|function)[]

    Set a mark to determine if the value is mutable or if an instance is an immutable, and it can also return a shallow copy function(AutoFreeze and Patches should both be disabled, Some patches operation might not be equivalent). When the mark function is (target) => 'immutable', it means all the objects in the state structure are immutable. In this specific case, you can totally turn on AutoFreeze and Patches. mark supports multiple marks, and the marks are executed in order, and the first mark that returns a value will be used. When a object tree node is marked by the mark function as mutable, all of its child nodes will also not be drafted by Mutative and will retain their original values.

create() - Currying

  • create draft
const [draft, finalize] = create(baseState);
draft.foobar.bar = "baz";
const state = finalize();

Support set options such as const [draft, finalize] = create(baseState, { enableAutoFreeze: true });

  • create producer
const produce = create((draft) => {
  draft.foobar.bar = "baz";
});
const state = produce(baseState);

Also support set options such as const produce = create((draft) => {}, { enableAutoFreeze: true });

apply()

Use apply() for applying patches to get the new state.

import { create, apply } from "mutative";
const baseState = {
  foo: "bar",
  list: [{ text: "todo" }],
};
const [state, patches, inversePatches] = create(
  baseState,
  (draft) => {
    draft.foo = "foobar";
    draft.list.push({ text: "learning" });
  },
  {
    enablePatches: true,
  },
);
const nextState = apply(baseState, patches);
expect(nextState).toEqual(state);
const prevState = apply(state, inversePatches);
expect(prevState).toEqual(baseState);

apply(state, patches, options)

The options parameter is optional and supports two types of configurations:

  1. Immutable options (similar to create options but without enablePatches):
    • strict - boolean, forbid accessing non-draftable values in strict mode
    • enableAutoFreeze - boolean, enable autoFreeze and return frozen state
    • mark - mark function to determine if a value is mutable/immutable
const baseState = { foo: { bar: "test" } };
// This will create a new state.
const result = apply(baseState, [
  {
    op: "replace",
    path: ["foo", "bar"],
    value: "test2",
  },
]);
expect(baseState).not.toEqual({ foo: { bar: "test2" } });
expect(result).toEqual({ foo: { bar: "test2" } });
  1. Mutable option(Mutative v1.2.0+):
    • mutable - boolean, if true the state will be mutated directly instead of creating a new state

Example with mutable option:

const baseState = { foo: { bar: "test" } };
// This will modify baseState directly
apply(
  baseState,
  [
    {
      op: "replace",
      path: ["foo", "bar"],
      value: "test2",
    },
  ],
  {
    mutable: true,
  },
);
expect(baseState).toEqual({ foo: { bar: "test2" } });

⚠️Note: The mutable option cannot be combined with other options. When using mutable option, apply() will return void instead of a new state.

current()

Get the current value from a draft.

  • For any draft where a child node has been modified, the state obtained by executing current() each time will be a new reference object.
  • For a draft where no child nodes have been modified, executing current() will always return the original state.

It is recommended to minimize the number of times current() is executed when performing read-only operations, ideally executing it only once.

const state = create({ a: { b: { c: 1 } }, d: { f: 1 } }, (draft) => {
  draft.a.b.c = 2;
  expect(current(draft.a)).toEqual({ b: { c: 2 } });
  // The node `a` has been modified.
  expect(current(draft.a) === current(draft.a)).toBeFalsy();
  // The node `d` has not been modified.
  expect(current(draft.d) === current(draft.d)).toBeTruthy();
});

current() is also the cheap way to search a large array of objects. Every object read through a draft becomes a draft of its own, so draft.list.find() pays for a draft per visited element. current(draft.list) is the original array while the recipe has not changed it; otherwise it is a copy that holds the current value of each changed element and the original object of every other one. Its indices are those of the draft, also after the recipe added, removed or moved elements. Search it and change the match through the draft. Its elements are not drafts, so the callback must only read them.

const state = create(baseState, (draft) => {
  const index = current(draft.list).findIndex((item) => item.text === "todo");
  draft.list[index].done = true;
});

original()

Get the original value from a draft.

const baseState = {
  foo: "bar",
  list: [{ text: "todo" }],
};
const state = create(baseState, (draft) => {
  draft.foo = "foobar";
  draft.list.push({ text: "learning" });
  expect(original(draft.list)).toEqual([{ text: "todo" }]);
});

original() reflects the state before the recipe's changes, so an index found in original(draft.list) no longer matches the draft once the recipe has added, removed or moved elements. To search a draft array, use current().

unsafe()

When strict mode is enabled, mutable data can only be accessed using unsafe().

const baseState = {
  list: [],
  date: new Date(),
};
const state = create(
  baseState,
  (draft) => {
    unsafe(() => {
      draft.date.setFullYear(2000);
    });
    // or return the mutable data:
    // const date = unsafe(() => draft.date);
  },
  {
    strict: true,
  },
);

If you'd like to enable strict mode by default in a development build and turn it off for production, you can use strict: process.env.NODE_ENV !== 'production'.

isDraft()

Check if a value is a draft.

const baseState = {
  date: new Date(),
  list: [{ text: "todo" }],
};
const state = create(baseState, (draft) => {
  expect(isDraft(draft.date)).toBeFalsy();
  expect(isDraft(draft.list)).toBeTruthy();
});

isDraftable()

Check if a value is draftable

const baseState = {
  date: new Date(),
  list: [{ text: "todo" }],
};
expect(isDraftable(baseState.date)).toBeFalsy();
expect(isDraftable(baseState.list)).toBeTruthy();

You can set a mark to determine if the value is draftable, and the mark function should be the same as passing in create() mark option.

rawReturn()

For return values that do not contain any drafts, you can use rawReturn() to wrap this return value to improve performance. It ensure that the return value is only returned explicitly.

const baseState = { id: "test" };
const state = create(baseState as { id: string } | undefined, (draft) => {
  return rawReturn(undefined);
});
expect(state).toBe(undefined);

If the return value mixes drafts, you should not use rawReturn().

const baseState = { a: 1, b: { c: 1 } };
const state = create(baseState, (draft) => {
  if (draft.b.c === 1) {
    return {
      ...draft,
      a: 2,
    };
  }
});
expect(state).toEqual({ a: 2, b: { c: 1 } });
expect(isDraft(state.b)).toBeFalsy();

If you use rawReturn(), we recommend that you enable strict mode in development.

const baseState = { a: 1, b: { c: 1 } };
const state = create(
  baseState,
  (draft) => {
    if (draft.b.c === 1) {
      return rawReturn({
        ...draft,
        a: 2,
      });
    }
  },
  {
    strict: true,
  },
);
// it will warn `The return value contains drafts, please don't use 'rawReturn()' to wrap the return value.` in strict mode.
expect(state).toEqual({ a: 2, b: { c: 1 } });
expect(isDraft(state.b)).toBeFalsy();

makeCreator()

makeCreator() only takes options as the first argument, resulting in a custom create() function.

const baseState = {
  foo: {
    bar: "str",
  },
};
const create = makeCreator({
  enablePatches: true,
});
const [state, patches, inversePatches] = create(baseState, (draft) => {
  draft.foo.bar = "new str";
});

markSimpleObject()

markSimpleObject() is a mark function that marks all objects as immutable.

const baseState = {
  foo: {
    bar: "str",
  },
  simpleObject: Object.create(null),
};
const state = create(
  baseState,
  (draft) => {
    draft.foo.bar = "new str";
    draft.simpleObject.a = "a";
  },
  {
    mark: markSimpleObject,
  },
);
expect(state.simpleObject).not.toBe(baseState.simpleObject);

View more API docs.

Using TypeScript

  • castDraft()
  • castImmutable()
  • castMutable()
  • Draft<T>
  • Immutable<T>
  • Patches
  • Patch
  • Options<O, F>

Integration with React

  • use-mutative - A 2-6x faster alternative to useState with spread operation
  • use-travel - A React hook for state time travel with undo, redo, reset and archive functionalities.
  • zustand-mutative - A Mutative middleware for Zustand enhances the efficiency of immutable state updates.

FAQs

  • I'm already using Immer, can I migrate smoothly to Mutative?

Yes. Unless you have to be compatible with Internet Explorer, Mutative supports almost all of Immer features, and you can easily migrate from Immer to Mutative.

Migration is also not possible for React Native that does not support Proxy. React Native uses a new JS engine during refactoring - Hermes, and it (if < v0.59 or when using the Hermes engine on React Native < v0.64) does not support Proxy on Android, but React Native v0.64 with the Hermes engine support Proxy.

  • Can Mutative be integrated with Redux?

Yes. Mutative supports return values for reducer, and redux-toolkit is considering support for configurable produce().

  • Which array methods run natively on drafts?

shift, unshift, splice and reverse move elements directly on the copy of a plain array without holes, including undefined elements; indexOf, lastIndexOf and includes search the current array natively; sort and join run natively on arrays of primitives. Removed elements are returned as drafts, patches replay in both directions, and a call that changes nothing keeps the state. Sparse arrays, array subclasses, an own constructor or Symbol.isConcatSpreadable, arrays under a custom mark, every method with a callback, at, slice, fill and copyWithin use the proxy path. An argument that can run user code, such as an object passed as fromIndex or as a splice index, is converted on the proxy path, so a conversion that changes the array is observed as the native methods observe it. The fast paths are made for data arrays: an accessor property defined on an array index is read as a data value, and the number and order of such getter calls, their re-entrant effects on the draft, and the identity of objects they return are not guaranteed to match element-by-element execution through the proxy.

Optimized searches give the same results as the proxy path, comparing elements as a read returns them. Drafts, values assigned in the recipe, non-draftable objects and primitives are found. An object of the base state is drafted when it is read, so it is never found, whether or not it was read before; use original() to search the base state, for example original(draft.list).indexOf(item). The search never reads a property of the value it is given.

In strict mode, outside unsafe(), optimized calls on an array that may hold objects take the proxy path unchanged, so reading a non-draftable element fails exactly as it always did; arrays of primitives, recognized with typeof alone, keep the native paths. Elements are moved and compared without being inspected, while the proxy path inspects each element it reads. An element that is itself a Proxy may therefore see fewer calls to its internal methods on the native paths, never more and never at other times; a revoked Proxy element that a search passes over, for example, does not throw there.

Draftable base elements removed or moved by these methods are drafted before they are exposed. Methods with callbacks, such as forEach, map, filter and find, go through the draft so that their callbacks see every change and can modify elements; use current() for read-only scans of large arrays.

  • Does Mutative support shared references?

Yes, Mutative supports shared references, but each path to a shared object gets its own independent draft. Modifications to one path do not automatically reflect in others. If you want to preserve shared references in the result, you must explicitly assign them (e.g., draft.b = draft.a). Read more details.

Migration from Immer to Mutative

mutative-compat - Mutative wrapper with full Immer API compatibility, you can use it to quickly migrate from Immer to Mutative.

  1. produce() -> create()

Mutative auto freezing option is disabled by default, Immer auto freezing option is enabled by default.

You need to check if auto freezing has any impact on your project. If it depends on auto freezing, you can enable it yourself in Mutative.

import produce from "immer";
const nextState = produce(baseState, (draft) => {
  draft[1].done = true;
  draft.push({ title: "something" });
});

Use Mutative

import { create } from "mutative";
const nextState = create(baseState, (draft) => {
  draft[1].done = true;
  draft.push({ title: "something" });
});
  1. Patches
import { produceWithPatches, applyPatches } from "immer";
enablePatches();
const baseState = {
  age: 33,
};
const [nextState, patches, inversePatches] = produceWithPatches(
  baseState,
  (draft) => {
    draft.age++;
  },
);
const state = applyPatches(nextState, inversePatches);
expect(state).toEqual(baseState);

Use Mutative

import { create, apply } from "mutative";
const baseState = {
  age: 33,
};
const [nextState, patches, inversePatches] = create(
  baseState,
  (draft) => {
    draft.age++;
  },
  {
    enablePatches: true,
  },
);
const state = apply(nextState, inversePatches);
expect(state).toEqual(baseState);
  1. Return undefined
import produce, { nothing } from "immer";
const nextState = produce(baseState, (draft) => {
  return nothing;
});

Use Mutative

import { create, rawReturn } from "mutative";
const nextState = create(baseState, (draft) => {
  return rawReturn(undefined);
});

Contributing

Mutative goal is to provide efficient and immutable updates. The focus is on performance improvements and providing better APIs for better development experiences. We are still working on it and welcome PRs that may help Mutative.

Development Workflow:

See Building and validating Mutative for the build pipeline, package checks, and bundle-size regression policy.

See the benchmark suite for the comparison of the current build with Mutative 1.3.0, Immer, and a hand-written reducer, matched freeze and patch modes, memory measurements, and CI regression budgets. Run pnpm benchmark:immer:check to validate every workload and pnpm benchmark:immer to measure them.

  • Clone Mutative repo.
  • Run pnpm install to install all the dependencies.
  • Run pnpm format to format the code.
  • pnpm test --watch runs an interactive test watcher.
  • Run pnpm commit to make a git commit.

License

Mutative is MIT licensed.

{
"by": "nnx",
"descendants": 0,
"id": 40245231,
"score": 1,
"time": 1714722414,
"title": "Mutative: Efficient immutable updates, more than 10x faster than Immer",
"type": "story",
"url": "https://github.com/unadlib/mutative"
}
{
"author": "unadlib",
"date": null,
"description": "Efficient immutable updates, about 3-6x faster than Immer. - unadlib/mutative",
"image": "https://opengraph.githubassets.com/5c92c8d132e925507f7e1cbbcd8c27f7f064155a6161f12962cf8795f54a624f/unadlib/mutative",
"logo": null,
"publisher": "GitHub",
"title": "GitHub - unadlib/mutative: Efficient immutable updates, about 3-6x faster than Immer.",
"url": "https://github.com/unadlib/mutative"
}
{
"url": "https://github.com/unadlib/mutative",
"title": "GitHub - unadlib/mutative: Efficient immutable updates, about 3-6x faster than Immer.",
"description": "Mutative - A JavaScript library for efficient immutable updates. Across 90 benchmarked workloads, it is about 3x faster than Immer with the same settings and 6x faster out of the box. The gap widens on large...",
"links": [
"https://github.com/unadlib/mutative"
],
"image": "https://opengraph.githubassets.com/5c92c8d132e925507f7e1cbbcd8c27f7f064155a6161f12962cf8795f54a624f/unadlib/mutative",
"content": "<div><article>\n<p><a target=\"_blank\" href=\"https://mutative.js.org/\"><img src=\"https://raw.githubusercontent.com/unadlib/mutative/main/website/static/img/%20mutative.png\" alt=\"Mutative Logo\" /></a></p>\n<p><a target=\"_blank\" href=\"https://github.com/unadlib/mutative/workflows/Node%20CI/badge.svg\"><img src=\"https://github.com/unadlib/mutative/workflows/Node%20CI/badge.svg\" alt=\"Node CI\" /></a>\n<a target=\"_blank\" href=\"https://coveralls.io/github/unadlib/mutative?branch=main\"><img src=\"https://camo.githubusercontent.com/97d5d032a6ba615babd09691e6b09d6cd6dce0fb1b5818b6915bf1e63c29767b/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f756e61646c69622f6d757461746976652f62616467652e7376673f6272616e63683d6d61696e\" alt=\"Coverage Status\" /></a>\n<a target=\"_blank\" href=\"https://www.npmjs.com/package/mutative\"><img src=\"https://camo.githubusercontent.com/6329a5646efdd29c0717567037922d16257f5edccb00539ba711f974ca6220b0/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6d757461746976652e737667\" alt=\"npm\" /></a>\n<a target=\"_blank\" href=\"https://npmtrends.com/mutative\"><img src=\"https://camo.githubusercontent.com/8d3144d1ba6f8d8525f7325e54da32e5511579c1fdaa0eaeaf619cbccf3f5d74/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f6d75746174697665\" alt=\"NPM Downloads\" /></a>\n<a target=\"_blank\" href=\"https://camo.githubusercontent.com/c2ebeceba0e19987280ddb451769446d540f5e7859d0b477997372edba0949c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f6d75746174697665\"><img src=\"https://camo.githubusercontent.com/c2ebeceba0e19987280ddb451769446d540f5e7859d0b477997372edba0949c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f6d75746174697665\" alt=\"license\" /></a></p>\n<p><strong>Mutative</strong> - A JavaScript library for efficient immutable updates. Across <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/reports/SUMMARY.md\">90 benchmarked workloads</a>, it is about 3x faster than Immer with the same settings and 6x faster out of the box.</p>\n<p>The gap widens on large arrays, where Mutative moves elements up to 1,125x faster than Immer. When copying dominates, such as updating objects with thousands of keys or inserting at the front of a large array, Mutative is even faster than hand-written spread reducers.</p>\n<p><strong>How does Mutative compare with the spread operation (hand-written reducers)?</strong></p>\n<ul>\n<li><a target=\"_blank\" href=\"https://www.richsnapp.com/article/2019/06-09-reduce-spread-anti-pattern\">The reduce ({...spread}) anti-pattern</a></li>\n<li><a target=\"_blank\" href=\"https://jonlinnell.co.uk/articles/spread-operator-performance?fbclid=IwAR0mElQwz2aOxl8rcsqoYwkcQDlcXcwuyIsTmTAbmyzrarysS8-BC1lSY9k\">How slow is the Spread operator in JavaScript?</a></li>\n</ul>\n<p>Mutative copies each object at most once per update, however many changes a recipe makes, and focuses on shallow copy optimization, more complete lazy drafts, finalization process optimization, and more.</p>\n<p></p><h2>Motivation</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#motivation\"></a><p></p>\n<p>Writing immutable updates by hand is usually difficult, prone to errors, and cumbersome. Immer helps us write simpler immutable updates with \"mutative\" logic.</p>\n<p>But its performance issue causes a runtime performance overhead. Immer enables auto-freeze by default, and such frozen immutable state is not common. In scenarios such as cross-processing, remote data transfer, etc., these immutable data must be constantly frozen.</p>\n<p>There are more parts that could be improved, such as better type inference, non-intrusive markup, support for more types of immutability, Safer immutability, <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/test/immer-non-support.test.ts\">more edge cases</a>, and so on.</p>\n<p>This is why Mutative was created.</p>\n<p></p><h2>Performance</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#performance\"></a><p></p>\n<blockquote>\n<p>Mutative passed all of Immer's test cases.</p>\n</blockquote>\n<p>The <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/README.md\">benchmark suite</a> times 93 workloads: Immer's own performance tests, array methods, reads, Map and Set values, object records, class instances, a deep path, patch application, returned values, and searches. It compares Mutative with Immer 11.1.18 and with reducers written by hand, after checking every result against those reducers. The <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/reports/SUMMARY.md\">performance summary</a> has the complete results, the method, and their limits.</p>\n<p>With matched settings, both freezing or both not and both generating patches or both not, Mutative was faster than Immer in 508 of 530 measured cases, 3.1x on geometric mean. With each library's defaults, Mutative without auto-freeze and Immer with it, Mutative was faster in 133 of 136 cases, 6.0x on geometric mean.</p>\n<p>Times are microseconds per update, medians of three runs on an Apple M1 Max with Node.js 24.16.0; lower is better. Mutative, the first Immer column, and the hand-written reducers run without auto-freeze; the second Immer column shows Immer's default.</p>\n<table>\n<thead>\n<tr>\n<th>Workload</th>\n<th>Rows</th>\n<th>Mutative</th>\n<th>Immer</th>\n<th>Immer, auto-freeze</th>\n<th>Hand-written</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Update one field of a small object</td>\n<td>—</td>\n<td>0.65</td>\n<td>1.02</td>\n<td>1.32</td>\n<td>0.05</td>\n</tr>\n<tr>\n<td>Update an array item found by ID</td>\n<td>100</td>\n<td>1.33</td>\n<td>1.99</td>\n<td>25.9</td>\n<td>0.84</td>\n</tr>\n<tr>\n<td>200 RTK Query-style updates</td>\n<td>—</td>\n<td>1,031</td>\n<td>1,180</td>\n<td>5,209</td>\n<td>361</td>\n</tr>\n<tr>\n<td>Read every row by index</td>\n<td>10,000</td>\n<td>5,210</td>\n<td>11,253</td>\n<td>11,513</td>\n<td>135</td>\n</tr>\n<tr>\n<td>Remove the first row</td>\n<td>10,000</td>\n<td>7.37</td>\n<td>8,292</td>\n<td>9,125</td>\n<td>1.43</td>\n</tr>\n<tr>\n<td>Update every row</td>\n<td>10,000</td>\n<td>7,008</td>\n<td>12,803</td>\n<td>16,454</td>\n<td>243</td>\n</tr>\n<tr>\n<td>Update one of 10,000 records</td>\n<td>10,000</td>\n<td>1,242</td>\n<td>2,172</td>\n<td>3,222</td>\n<td>2,177</td>\n</tr>\n<tr>\n<td>Insert into a 1,000-property object</td>\n<td>—</td>\n<td>53.0</td>\n<td>151</td>\n<td>238</td>\n<td>131</td>\n</tr>\n<tr>\n<td>Update a Map value</td>\n<td>10,000</td>\n<td>553</td>\n<td>557</td>\n<td>774</td>\n<td>548</td>\n</tr>\n<tr>\n<td>Add a number to a Set</td>\n<td>10,000</td>\n<td>1,013</td>\n<td>1,502</td>\n<td>1,605</td>\n<td>72.4</td>\n</tr>\n<tr>\n<td>Update a class instance</td>\n<td>10,000</td>\n<td>3.19</td>\n<td>3.74</td>\n<td>673</td>\n<td>1.49</td>\n</tr>\n<tr>\n<td>Update a value ten levels deep</td>\n<td>—</td>\n<td>3.70</td>\n<td>4.75</td>\n<td>8.27</td>\n<td>0.87</td>\n</tr>\n<tr>\n<td>Apply patches to 10% of rows</td>\n<td>10,000</td>\n<td>1,558</td>\n<td>1,465</td>\n<td>2,440</td>\n<td>—</td>\n</tr>\n<tr>\n<td>Return draft.filter()</td>\n<td>10,000</td>\n<td>2,444</td>\n<td>4,188</td>\n<td>4,645</td>\n<td>80.2</td>\n</tr>\n<tr>\n<td>Return a new state</td>\n<td>10,000</td>\n<td>2,914</td>\n<td>8,272</td>\n<td>0.89</td>\n<td>0.06</td>\n</tr>\n<tr>\n<td>Return it with rawReturn()</td>\n<td>10,000</td>\n<td>0.30</td>\n<td>7,865</td>\n<td>0.89</td>\n<td>0.06</td>\n</tr>\n</tbody>\n</table>\n<p>The record and 1,000-property rows ran each library in a process of its own, because code that ran earlier in a process changes how fast V8 copies such wide objects. Immer has no <code>rawReturn()</code> and returns the same plain value in the last two rows. Immer was faster in 11 of the 530 matched cases: when returning a new state built from frozen data, which Immer does not search for drafts; when applying patches that replace nested values, by 5-7%; and, with auto-freeze, when updating a class instance with 1,000 fields.</p>\n<p>Run <code>pnpm benchmark:immer</code> to measure the suite; the <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/README.md\">benchmark guide</a> describes its options.</p>\n<p></p><h3>Large arrays</h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#large-arrays\"></a><p></p>\n<p>At 1,000 and 10,000 rows the gap grows. Against Immer without its array-method plugin, Mutative was faster in 164 of 176 cases at these sizes, 3.5x on geometric mean, and faster in every case that moves elements:</p>\n<table>\n<thead>\n<tr>\n<th>Auto-freeze</th>\n<th>Patches</th>\n<th>All workloads, 1,000 rows</th>\n<th>All workloads, 10,000 rows</th>\n<th>Moves, 1,000 rows</th>\n<th>Moves, 10,000 rows</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>off</td>\n<td>off</td>\n<td>7.3x</td>\n<td>8.8x</td>\n<td>212x</td>\n<td>472x</td>\n</tr>\n<tr>\n<td>off</td>\n<td>on</td>\n<td>3.9x</td>\n<td>4.2x</td>\n<td>6.7x</td>\n<td>7.7x</td>\n</tr>\n<tr>\n<td>on</td>\n<td>off</td>\n<td>2.6x</td>\n<td>2.3x</td>\n<td>32x</td>\n<td>35x</td>\n</tr>\n<tr>\n<td>on</td>\n<td>on</td>\n<td>1.9x</td>\n<td>1.7x</td>\n<td>6.2x</td>\n<td>6.8x</td>\n</tr>\n</tbody>\n</table>\n<p>Each value is the geometric mean of Immer's time over Mutative's; moves are <code>shift</code>, <code>unshift</code>, <code>splice</code> insertion, and <code>reverse</code>. Mutative moves elements natively on its copy, while Immer moves each one through its draft proxy: removing the first of 10,000 rows took 7.37 µs against 8,292 µs, 1,125x. With patches, both libraries emit one patch per moved index, which bounds the gain to 6-8x. The <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/reports/SUMMARY.md\">performance summary</a> breaks these results down by scenario.</p>\n<p></p><h3>With patches</h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#with-patches\"></a><p></p>\n<p>With patches on and auto-freeze off, Mutative was faster than Immer in 126 of 129 cases and never slower, 3.1x on geometric mean, and faster than Mutative 1.3.0 in 103 and within 5% in the rest, 3.7x. In every array case measured it was faster than both: 4.0x Immer and 13x Mutative 1.3.0 on geometric mean. Patches cost little when an update changes a few paths: pushing a row and inserting a property at 10,000 rows took 65.2 µs with patches against 64.8 µs without. Moving elements emits one patch per moved index in every library, so removing the first of 10,000 rows produces 10,000 forward and 10,000 inverse patches.</p>\n<p>Times are microseconds per update with patches on and auto-freeze off:</p>\n<table>\n<thead>\n<tr>\n<th>Workload</th>\n<th>Rows</th>\n<th>Mutative</th>\n<th>Immer</th>\n<th>Mutative 1.3.0</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Push a row and insert a property</td>\n<td>10,000</td>\n<td>65.2</td>\n<td>370</td>\n<td>212</td>\n</tr>\n<tr>\n<td>Update an array item found by ID</td>\n<td>100</td>\n<td>2.17</td>\n<td>3.57</td>\n<td>3.85</td>\n</tr>\n<tr>\n<td>200 RTK Query-style updates</td>\n<td>—</td>\n<td>1,325</td>\n<td>1,520</td>\n<td>2,172</td>\n</tr>\n<tr>\n<td>Remove the first row with <code>splice</code></td>\n<td>100</td>\n<td>13.5</td>\n<td>86.5</td>\n<td>3,108</td>\n</tr>\n<tr>\n<td>Insert a row in the middle</td>\n<td>100</td>\n<td>9.91</td>\n<td>51.3</td>\n<td>1,297</td>\n</tr>\n<tr>\n<td>Sort rows</td>\n<td>100</td>\n<td>108</td>\n<td>217</td>\n<td>2,622</td>\n</tr>\n<tr>\n<td>Remove the first row with <code>shift</code></td>\n<td>10,000</td>\n<td>1,227</td>\n<td>10,117</td>\n<td>282,588</td>\n</tr>\n<tr>\n<td>Reverse the rows</td>\n<td>10,000</td>\n<td>1,226</td>\n<td>10,167</td>\n<td>286,207</td>\n</tr>\n<tr>\n<td>Update every row</td>\n<td>10,000</td>\n<td>11,591</td>\n<td>22,553</td>\n<td>21,851</td>\n</tr>\n<tr>\n<td>Update one of 10,000 records</td>\n<td>10,000</td>\n<td>1,236</td>\n<td>2,165</td>\n<td>1,230</td>\n</tr>\n</tbody>\n</table>\n<p>Immer's optional <code>enableArrayMethods()</code> plugin also runs array methods on the draft's copy, and with patches it comes within 1.05-1.74x of Mutative when moving elements of 1,000 or 10,000 rows. These comparisons leave it off because in Immer 11.1.18 it breaks guarantees that Immer otherwise keeps: <code>shift</code>, <code>pop</code> and <code>splice</code> return raw base objects, so editing a removed object changes the previous state; reordering can expose original objects the same way and leave revoked drafts in the result; and its forward patches can fail to replay. In <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/test/immer-array-methods.md\">the audit</a>, 123 of 4,913 three-step operation sequences break with the plugin and none without it. The <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/reports/SUMMARY.md\">performance summary</a> reports Immer with the plugin separately.</p>\n<p></p><h3>Bundle size</h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#bundle-size\"></a><p></p>\n<p>Mutative ships patches, <code>Map</code>/<code>Set</code> support and native array methods built in; Immer provides them as opt-in plugins. The following Brotli sizes were measured with esbuild 0.24.0 from the ESM entry that bundlers resolve for each library (Immer 11.1.21 <code>dist/immer.mjs</code>; Mutative <code>dist/mutative.esm.mjs</code> at source <code>18a0ea7</code>), with <code>process.env.NODE_ENV</code> defined as <code>production</code>, bundled for the browser with <code>--minify --target=es2018 --format=esm</code>. The first row imports only <code>produce</code> or <code>create</code>. The second adds <code>applyPatches</code>, <code>current</code> and <code>original</code> with Immer's <code>enablePatches</code>, <code>enableMapSet</code> and <code>enableArrayMethods</code>, and <code>apply</code>, <code>current</code> and <code>original</code> for Mutative.</p>\n<table>\n<thead>\n<tr>\n<th>Bundle</th>\n<th>Immer</th>\n<th>Mutative</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>produce</code> / <code>create</code> only</td>\n<td>3.6 kB</td>\n<td>7.4 kB</td>\n</tr>\n<tr>\n<td>With patches, <code>Map</code>/<code>Set</code> and array methods</td>\n<td>6.4 kB</td>\n<td>7.9 kB</td>\n</tr>\n</tbody>\n</table>\n<p>Mutative's <code>create</code> includes patches, <code>Map</code>/<code>Set</code> support and the native array methods even when a recipe does not use them: they are part of <code>create</code>, not separate imports, so bundlers cannot drop them. The difference buys the draft fast paths and the native array methods measured in the <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/reports/SUMMARY.md\">performance summary</a>, which also records the artifact sizes of each measured source. See the <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#faqs\">array methods FAQ</a> for the supported fast paths and their contract, and the <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/test/immer-array-methods.md\">Immer regression cases</a> for the behavior of its array-method plugin.</p>\n<p></p><h2>Features and Benefits</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#features-and-benefits\"></a><p></p>\n<ul>\n<li><strong>Mutation makes immutable updates</strong> - Immutable data structures supporting objects, arrays, Sets and Maps.</li>\n<li><strong>High performance</strong> - About 6x faster than Immer out of the box, and faster than hand-written spreads when updating objects with thousands of keys or inserting at the front of large arrays.</li>\n<li><strong>Optional freezing state</strong> - No freezing of immutable data by default.</li>\n<li><strong>Support for JSON Patch</strong> - Full compliance with JSON Patch specification.</li>\n<li><strong>Custom shallow copy</strong> - Support for more types of immutable data.</li>\n<li><strong>Support mark for immutable and mutable data</strong> - Allows for non-invasive marking.</li>\n<li><strong>Safer mutable data access in strict mode</strong> - It brings more secure immutable updates.</li>\n<li><strong>Support for reducer</strong> - Support reducer function and any other immutable state library.</li>\n</ul>\n<p></p><h2>Difference between Mutative and Immer</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#difference-between-mutative-and-immer\"></a><p></p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Mutative</th>\n<th>Immer</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Custom shallow copy</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>Strict mode</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>No data freeze by default</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>Non-invasive marking</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>Complete freeze data</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>Non-global config</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>async draft function</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>Fully compatible with JSON Patch spec</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n<tr>\n<td>new Set methods(Mutative v1.1.0+)</td>\n<td>✅</td>\n<td>❌</td>\n</tr>\n</tbody>\n</table>\n<p>Mutative has fewer bugs such as accidental draft escapes than Immer, <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/test/immer-non-support.test.ts\">view details</a>.</p>\n<p></p><h2>Installation</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#installation\"></a><p></p>\n<p>Yarn</p>\n<p>NPM</p>\n<p>CDN</p>\n<ul>\n<li>Unpkg: <code>&lt;script src=\"https://unpkg.com/mutative\"&gt;&lt;/script&gt;</code></li>\n<li>JSDelivr: <code>&lt;script src=\"https://cdn.jsdelivr.net/npm/mutative\"&gt;&lt;/script&gt;</code></li>\n<li>ES module: <code>import { create } from 'https://unpkg.com/mutative/dist/mutative.esm.production.min.mjs';</code></li>\n</ul>\n<p>The package's other ESM files read <code>process.env.NODE_ENV</code>, which bundlers and Node.js provide; a browser without a bundler needs the production file above.</p>\n<p></p><h2>Usage</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#usage\"></a><p></p>\n<div><pre><span>import</span> <span>{</span> <span>create</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>\"bar\"</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"coding\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>list</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>text</span>: <span>\"learning\"</span> <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>not</span><span>.</span><span>toBe</span><span>(</span><span>baseState</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>.</span><span>list</span><span>)</span><span>.</span><span>not</span><span>.</span><span>toBe</span><span>(</span><span>baseState</span><span>.</span><span>list</span><span>)</span><span>;</span></pre></div>\n<p><code>create(baseState, (draft) =&gt; void, options?: Options): newState</code></p>\n<p>The first argument of <code>create()</code> is the base state. Mutative drafts it and passes it to the arguments of the draft function, and performs the draft mutation until the draft function finishes, then Mutative will finalize it and produce the new state.</p>\n<p>Use <code>create()</code> for more advanced features by <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#createstate-fn-options\">setting <code>options</code></a>.</p>\n<p></p><h2>APIs</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#apis\"></a><p></p>\n<ul>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#create\"><code>create()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#apply\"><code>apply()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#current\"><code>current()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#original\"><code>original()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#unsafe\"><code>unsafe()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#isdraft\"><code>isDraft()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#isdraftable\"><code>isDraftable()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#rawreturn\"><code>rawReturn()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#makecreator\"><code>makeCreator()</code></a></li>\n<li><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#marksimpleobject\"><code>markSimpleObject()</code></a></li>\n</ul>\n<p></p><h3><code>create()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#create\"></a><p></p>\n<p>Use <code>create()</code> for draft mutation to get a new state, which also supports currying.</p>\n<div><pre><span>import</span> <span>{</span> <span>create</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>\"bar\"</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foo</span> <span>=</span> <span>\"foobar\"</span><span>;</span>\n <span>draft</span><span>.</span><span>list</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>text</span>: <span>\"learning\"</span> <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p>In this basic example, the changes to the draft are 'mutative' within the draft callback, and <code>create()</code> is finally executed with a new immutable state.</p>\n<p></p><h4><code>create(state, fn, options)</code></h4><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#createstate-fn-options\"></a><p></p>\n<blockquote>\n<p>Then options is optional.</p>\n</blockquote>\n<ul>\n<li>\n<p>strict - <code>boolean</code>, the default is false.</p>\n<blockquote>\n<p>Forbid accessing non-draftable values in strict mode(unless using <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#unsafe\">unsafe()</a>).</p>\n</blockquote>\n<blockquote>\n<p>When strict mode is enabled, mutable data can only be accessed using <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#unsafe\"><code>unsafe()</code></a>.</p>\n</blockquote>\n<blockquote>\n<p><strong>It is recommended to enable <code>strict</code> in development mode and disable <code>strict</code> in production mode.</strong> This will ensure safe explicit returns and also keep good performance in the production build. If the value that does not mix any current draft or is <code>undefined</code> is returned, then use <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#rawreturn\">rawReturn()</a>.</p>\n</blockquote>\n<blockquote>\n<p>If you'd like to enable strict mode by default in a development build and turn it off for production, you can use <code>strict: process.env.NODE_ENV !== 'production'</code>.</p>\n</blockquote>\n<blockquote>\n<p>In development builds, strict mode also warns once when a recipe leaves 1,000 or more drafts unchanged, as a search through a large draft array does. See <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#current\"><code>current()</code></a> for searching without creating drafts.</p>\n</blockquote>\n</li>\n<li>\n<p>enablePatches - <code>boolean | { pathAsArray?: boolean; arrayLengthAssignment?: boolean; }</code>, the default is false.</p>\n<blockquote>\n<p>Enable patch, and return the patches/inversePatches.</p>\n</blockquote>\n<blockquote>\n<p>If you need to set the shape of the generated patch in more detail, then you can set <code>pathAsArray</code> and <code>arrayLengthAssignment</code>。<code>pathAsArray</code> default value is <code>true</code>, if it's <code>true</code>, the path will be an array, otherwise it is a string; <code>arrayLengthAssignment</code> default value is <code>true</code>, if it's <code>true</code>, the array length will be included in the patches, otherwise no include array length(<strong>NOTE</strong>: If <code>arrayLengthAssignment</code> is <code>false</code>, it is fully compatible with JSON Patch spec, but it may have additional performance loss), <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/issues/6\">view related discussions</a>.</p>\n</blockquote>\n</li>\n<li>\n<p>enableAutoFreeze - <code>boolean</code>, the default is false.</p>\n<blockquote>\n<p>Enable autoFreeze, and return frozen state, and enable circular reference checking only in <code>development</code> mode.</p>\n</blockquote>\n</li>\n<li>\n<p>mark - <code>(target) =&gt; ('mutable'|'immutable'|function) | (target) =&gt; ('mutable'|'immutable'|function)[]</code></p>\n<blockquote>\n<p>Set a mark to determine if the value is mutable or if an instance is an immutable, and it can also return a shallow copy function(<code>AutoFreeze</code> and <code>Patches</code> should both be disabled, Some patches operation might not be equivalent).\nWhen the mark function is (target) =&gt; 'immutable', it means all the objects in the state structure are immutable. In this specific case, you can totally turn on <code>AutoFreeze</code> and <code>Patches</code>.\n<code>mark</code> supports multiple marks, and the marks are executed in order, and the first mark that returns a value will be used.\nWhen a object tree node is marked by the <code>mark</code> function as <code>mutable</code>, all of its child nodes will also not be drafted by Mutative and will retain their original values.</p>\n</blockquote>\n</li>\n</ul>\n<p></p><h4><code>create()</code> - Currying</h4><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#create---currying\"></a><p></p>\n<ul>\n<li>create <code>draft</code></li>\n</ul>\n<div><pre><span>const</span> <span>[</span><span>draft</span><span>,</span> <span>finalize</span><span>]</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>)</span><span>;</span>\n<span>draft</span><span>.</span><span>foobar</span><span>.</span><span>bar</span> <span>=</span> <span>\"baz\"</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>finalize</span><span>(</span><span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>Support set options such as <code>const [draft, finalize] = create(baseState, { enableAutoFreeze: true });</code></p>\n</blockquote>\n<ul>\n<li>create <code>producer</code></li>\n</ul>\n<div><pre><span>const</span> <span>produce</span> <span>=</span> <span>create</span><span>(</span><span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foobar</span><span>.</span><span>bar</span> <span>=</span> <span>\"baz\"</span><span>;</span>\n<span>}</span><span>)</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>produce</span><span>(</span><span>baseState</span><span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>Also support set options such as <code>const produce = create((draft) =&gt; {}, { enableAutoFreeze: true });</code></p>\n</blockquote>\n<p></p><h3><code>apply()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#apply\"></a><p></p>\n<p>Use <code>apply()</code> for applying patches to get the new state.</p>\n<div><pre><span>import</span> <span>{</span> <span>create</span><span>,</span> <span>apply</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>\"bar\"</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>[</span><span>state</span><span>,</span> <span>patches</span><span>,</span> <span>inversePatches</span><span>]</span> <span>=</span> <span>create</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foo</span> <span>=</span> <span>\"foobar\"</span><span>;</span>\n <span>draft</span><span>.</span><span>list</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>text</span>: <span>\"learning\"</span> <span>}</span><span>)</span><span>;</span>\n <span>}</span><span>,</span>\n <span>{</span>\n <span>enablePatches</span>: <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>const</span> <span>nextState</span> <span>=</span> <span>apply</span><span>(</span><span>baseState</span><span>,</span> <span>patches</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>nextState</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>state</span><span>)</span><span>;</span>\n<span>const</span> <span>prevState</span> <span>=</span> <span>apply</span><span>(</span><span>state</span><span>,</span> <span>inversePatches</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>prevState</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>baseState</span><span>)</span><span>;</span></pre></div>\n<p></p><h4><code>apply(state, patches, options)</code></h4><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#applystate-patches-options\"></a><p></p>\n<p>The options parameter is optional and supports two types of configurations:</p>\n<ol>\n<li>Immutable options (similar to create options but without <code>enablePatches</code>):\n<ul>\n<li><code>strict</code> - <code>boolean</code>, forbid accessing non-draftable values in strict mode</li>\n<li><code>enableAutoFreeze</code> - <code>boolean</code>, enable autoFreeze and return frozen state</li>\n<li><code>mark</code> - mark function to determine if a value is mutable/immutable</li>\n</ul>\n</li>\n</ol>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span> <span>foo</span>: <span>{</span> <span>bar</span>: <span>\"test\"</span> <span>}</span> <span>}</span><span>;</span>\n<span>// This will create a new state.</span>\n<span>const</span> <span>result</span> <span>=</span> <span>apply</span><span>(</span><span>baseState</span><span>,</span> <span>[</span>\n <span>{</span>\n <span>op</span>: <span>\"replace\"</span><span>,</span>\n <span>path</span>: <span>[</span><span>\"foo\"</span><span>,</span> <span>\"bar\"</span><span>]</span><span>,</span>\n <span>value</span>: <span>\"test2\"</span><span>,</span>\n <span>}</span><span>,</span>\n<span>]</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>baseState</span><span>)</span><span>.</span><span>not</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>foo</span>: <span>{</span> <span>bar</span>: <span>\"test2\"</span> <span>}</span> <span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>result</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>foo</span>: <span>{</span> <span>bar</span>: <span>\"test2\"</span> <span>}</span> <span>}</span><span>)</span><span>;</span></pre></div>\n<ol>\n<li>Mutable option(Mutative v1.2.0+):\n<ul>\n<li><code>mutable</code> - <code>boolean</code>, if true the state will be mutated directly instead of creating a new state</li>\n</ul>\n</li>\n</ol>\n<p>Example with mutable option:</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span> <span>foo</span>: <span>{</span> <span>bar</span>: <span>\"test\"</span> <span>}</span> <span>}</span><span>;</span>\n<span>// This will modify baseState directly</span>\n<span>apply</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>[</span>\n <span>{</span>\n <span>op</span>: <span>\"replace\"</span><span>,</span>\n <span>path</span>: <span>[</span><span>\"foo\"</span><span>,</span> <span>\"bar\"</span><span>]</span><span>,</span>\n <span>value</span>: <span>\"test2\"</span><span>,</span>\n <span>}</span><span>,</span>\n <span>]</span><span>,</span>\n <span>{</span>\n <span>mutable</span>: <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>baseState</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>foo</span>: <span>{</span> <span>bar</span>: <span>\"test2\"</span> <span>}</span> <span>}</span><span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>⚠️Note: The mutable option cannot be combined with other options. When using mutable option, apply() will return void instead of a new state.</p>\n</blockquote>\n<p></p><h3><code>current()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#current\"></a><p></p>\n<p>Get the current value from a draft.</p>\n<ul>\n<li>For any draft where a child node has been modified, the state obtained by executing current() each time will be a new reference object.</li>\n<li>For a draft where no child nodes have been modified, executing current() will always return the original state.</li>\n</ul>\n<blockquote>\n<p>It is recommended to minimize the number of times current() is executed when performing read-only operations, ideally executing it only once.</p>\n</blockquote>\n<div><pre><span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>{</span> <span>a</span>: <span>{</span> <span>b</span>: <span>{</span> <span>c</span>: <span>1</span> <span>}</span> <span>}</span><span>,</span> <span>d</span>: <span>{</span> <span>f</span>: <span>1</span> <span>}</span> <span>}</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>a</span><span>.</span><span>b</span><span>.</span><span>c</span> <span>=</span> <span>2</span><span>;</span>\n <span>expect</span><span>(</span><span>current</span><span>(</span><span>draft</span><span>.</span><span>a</span><span>)</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>b</span>: <span>{</span> <span>c</span>: <span>2</span> <span>}</span> <span>}</span><span>)</span><span>;</span>\n <span>// The node `a` has been modified.</span>\n <span>expect</span><span>(</span><span>current</span><span>(</span><span>draft</span><span>.</span><span>a</span><span>)</span> <span>===</span> <span>current</span><span>(</span><span>draft</span><span>.</span><span>a</span><span>)</span><span>)</span><span>.</span><span>toBeFalsy</span><span>(</span><span>)</span><span>;</span>\n <span>// The node `d` has not been modified.</span>\n <span>expect</span><span>(</span><span>current</span><span>(</span><span>draft</span><span>.</span><span>d</span><span>)</span> <span>===</span> <span>current</span><span>(</span><span>draft</span><span>.</span><span>d</span><span>)</span><span>)</span><span>.</span><span>toBeTruthy</span><span>(</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p><code>current()</code> is also the cheap way to search a large array of objects. Every object read through a draft becomes a draft of its own, so <code>draft.list.find()</code> pays for a draft per visited element. <code>current(draft.list)</code> is the original array while the recipe has not changed it; otherwise it is a copy that holds the current value of each changed element and the original object of every other one. Its indices are those of the draft, also after the recipe added, removed or moved elements. Search it and change the match through the draft. Its elements are not drafts, so the callback must only read them.</p>\n<div><pre><span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>const</span> <span>index</span> <span>=</span> <span>current</span><span>(</span><span>draft</span><span>.</span><span>list</span><span>)</span><span>.</span><span>findIndex</span><span>(</span><span>(</span><span>item</span><span>)</span> <span>=&gt;</span> <span>item</span><span>.</span><span>text</span> <span>===</span> <span>\"todo\"</span><span>)</span><span>;</span>\n <span>draft</span><span>.</span><span>list</span><span>[</span><span>index</span><span>]</span><span>.</span><span>done</span> <span>=</span> <span>true</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p></p><h3><code>original()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#original\"></a><p></p>\n<p>Get the original value from a draft.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>\"bar\"</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foo</span> <span>=</span> <span>\"foobar\"</span><span>;</span>\n <span>draft</span><span>.</span><span>list</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>text</span>: <span>\"learning\"</span> <span>}</span><span>)</span><span>;</span>\n <span>expect</span><span>(</span><span>original</span><span>(</span><span>draft</span><span>.</span><span>list</span><span>)</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p><code>original()</code> reflects the state before the recipe's changes, so an index found in <code>original(draft.list)</code> no longer matches the draft once the recipe has added, removed or moved elements. To search a draft array, use <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#current\"><code>current()</code></a>.</p>\n<p></p><h3><code>unsafe()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#unsafe\"></a><p></p>\n<p>When strict mode is enabled, mutable data can only be accessed using <code>unsafe()</code>.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>list</span>: <span>[</span><span>]</span><span>,</span>\n <span>date</span>: <span>new</span> <span>Date</span><span>(</span><span>)</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>unsafe</span><span>(</span><span>(</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>date</span><span>.</span><span>setFullYear</span><span>(</span><span>2000</span><span>)</span><span>;</span>\n <span>}</span><span>)</span><span>;</span>\n <span>// or return the mutable data:</span>\n <span>// const date = unsafe(() =&gt; draft.date);</span>\n <span>}</span><span>,</span>\n <span>{</span>\n <span>strict</span>: <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>If you'd like to enable strict mode by default in a development build and turn it off for production, you can use <code>strict: process.env.NODE_ENV !== 'production'</code>.</p>\n</blockquote>\n<p></p><h3><code>isDraft()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#isdraft\"></a><p></p>\n<p>Check if a value is a draft.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>date</span>: <span>new</span> <span>Date</span><span>(</span><span>)</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>expect</span><span>(</span><span>isDraft</span><span>(</span><span>draft</span><span>.</span><span>date</span><span>)</span><span>)</span><span>.</span><span>toBeFalsy</span><span>(</span><span>)</span><span>;</span>\n <span>expect</span><span>(</span><span>isDraft</span><span>(</span><span>draft</span><span>.</span><span>list</span><span>)</span><span>)</span><span>.</span><span>toBeTruthy</span><span>(</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p></p><h3><code>isDraftable()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#isdraftable\"></a><p></p>\n<p>Check if a value is draftable</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>date</span>: <span>new</span> <span>Date</span><span>(</span><span>)</span><span>,</span>\n <span>list</span>: <span>[</span><span>{</span> <span>text</span>: <span>\"todo\"</span> <span>}</span><span>]</span><span>,</span>\n<span>}</span><span>;</span>\n<span>expect</span><span>(</span><span>isDraftable</span><span>(</span><span>baseState</span><span>.</span><span>date</span><span>)</span><span>)</span><span>.</span><span>toBeFalsy</span><span>(</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>isDraftable</span><span>(</span><span>baseState</span><span>.</span><span>list</span><span>)</span><span>)</span><span>.</span><span>toBeTruthy</span><span>(</span><span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>You can set a mark to determine if the value is draftable, and the mark function should be the same as passing in <code>create()</code> mark option.</p>\n</blockquote>\n<p></p><h3><code>rawReturn()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#rawreturn\"></a><p></p>\n<p>For return values that do not contain any drafts, you can use <code>rawReturn()</code> to wrap this return value to improve performance. It ensure that the return value is only returned explicitly.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span> <span>id</span>: <span>\"test\"</span> <span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span> <span>as</span> <span>{</span> <span>id</span>: <span>string</span> <span>}</span> <span>|</span> <span>undefined</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>return</span> <span>rawReturn</span><span>(</span><span>undefined</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>toBe</span><span>(</span><span>undefined</span><span>)</span><span>;</span></pre></div>\n<blockquote>\n<p>If the return value mixes drafts, you should not use <code>rawReturn()</code>.</p>\n</blockquote>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span> <span>a</span>: <span>1</span><span>,</span> <span>b</span>: <span>{</span> <span>c</span>: <span>1</span> <span>}</span> <span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>if</span> <span>(</span><span>draft</span><span>.</span><span>b</span><span>.</span><span>c</span> <span>===</span> <span>1</span><span>)</span> <span>{</span>\n <span>return</span> <span>{</span>\n ...<span>draft</span><span>,</span>\n <span>a</span>: <span>2</span><span>,</span>\n <span>}</span><span>;</span>\n <span>}</span>\n<span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>a</span>: <span>2</span><span>,</span> <span>b</span>: <span>{</span> <span>c</span>: <span>1</span> <span>}</span> <span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>isDraft</span><span>(</span><span>state</span><span>.</span><span>b</span><span>)</span><span>)</span><span>.</span><span>toBeFalsy</span><span>(</span><span>)</span><span>;</span></pre></div>\n<p>If you use <code>rawReturn()</code>, we recommend that you enable <code>strict</code> mode in development.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span> <span>a</span>: <span>1</span><span>,</span> <span>b</span>: <span>{</span> <span>c</span>: <span>1</span> <span>}</span> <span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>if</span> <span>(</span><span>draft</span><span>.</span><span>b</span><span>.</span><span>c</span> <span>===</span> <span>1</span><span>)</span> <span>{</span>\n <span>return</span> <span>rawReturn</span><span>(</span><span>{</span>\n ...<span>draft</span><span>,</span>\n <span>a</span>: <span>2</span><span>,</span>\n <span>}</span><span>)</span><span>;</span>\n <span>}</span>\n <span>}</span><span>,</span>\n <span>{</span>\n <span>strict</span>: <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>// it will warn `The return value contains drafts, please don't use 'rawReturn()' to wrap the return value.` in strict mode.</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>{</span> <span>a</span>: <span>2</span><span>,</span> <span>b</span>: <span>{</span> <span>c</span>: <span>1</span> <span>}</span> <span>}</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>isDraft</span><span>(</span><span>state</span><span>.</span><span>b</span><span>)</span><span>)</span><span>.</span><span>toBeFalsy</span><span>(</span><span>)</span><span>;</span></pre></div>\n<p></p><h3><code>makeCreator()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#makecreator\"></a><p></p>\n<p><code>makeCreator()</code> only takes <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#createstate-fn-options\">options</a> as the first argument, resulting in a custom <code>create()</code> function.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>{</span>\n <span>bar</span>: <span>\"str\"</span><span>,</span>\n <span>}</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>create</span> <span>=</span> <span>makeCreator</span><span>(</span><span>{</span>\n <span>enablePatches</span>: <span>true</span><span>,</span>\n<span>}</span><span>)</span><span>;</span>\n<span>const</span> <span>[</span><span>state</span><span>,</span> <span>patches</span><span>,</span> <span>inversePatches</span><span>]</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foo</span><span>.</span><span>bar</span> <span>=</span> <span>\"new str\"</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p></p><h3><code>markSimpleObject()</code></h3><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#marksimpleobject\"></a><p></p>\n<p><code>markSimpleObject()</code> is a mark function that marks all objects as immutable.</p>\n<div><pre><span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>foo</span>: <span>{</span>\n <span>bar</span>: <span>\"str\"</span><span>,</span>\n <span>}</span><span>,</span>\n <span>simpleObject</span>: <span>Object</span><span>.</span><span>create</span><span>(</span><span>null</span><span>)</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>create</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>foo</span><span>.</span><span>bar</span> <span>=</span> <span>\"new str\"</span><span>;</span>\n <span>draft</span><span>.</span><span>simpleObject</span><span>.</span><span>a</span> <span>=</span> <span>\"a\"</span><span>;</span>\n <span>}</span><span>,</span>\n <span>{</span>\n <span>mark</span>: <span>markSimpleObject</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>.</span><span>simpleObject</span><span>)</span><span>.</span><span>not</span><span>.</span><span>toBe</span><span>(</span><span>baseState</span><span>.</span><span>simpleObject</span><span>)</span><span>;</span></pre></div>\n<p><a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/docs/README.md\">View more API docs</a>.</p>\n<p></p><h2>Using TypeScript</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#using-typescript\"></a><p></p>\n<ul>\n<li><code>castDraft()</code></li>\n<li><code>castImmutable()</code></li>\n<li><code>castMutable()</code></li>\n<li><code>Draft&lt;T&gt;</code></li>\n<li><code>Immutable&lt;T&gt;</code></li>\n<li><code>Patches</code></li>\n<li><code>Patch</code></li>\n<li><code>Options&lt;O, F&gt;</code></li>\n</ul>\n<p></p><h2>Integration with React</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#integration-with-react\"></a><p></p>\n<ul>\n<li><a target=\"_blank\" href=\"https://github.com/mutativejs/use-mutative\">use-mutative</a> - A 2-6x faster alternative to useState with spread operation</li>\n<li><a target=\"_blank\" href=\"https://github.com/mutativejs/use-travel\">use-travel</a> - A React hook for state time travel with undo, redo, reset and archive functionalities.</li>\n<li><a target=\"_blank\" href=\"https://github.com/mutativejs/zustand-mutative\">zustand-mutative</a> - A Mutative middleware for Zustand enhances the efficiency of immutable state updates.</li>\n</ul>\n<p></p><h2>FAQs</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#faqs\"></a><p></p>\n<ul>\n<li>I'm already using Immer, can I migrate smoothly to Mutative?</li>\n</ul>\n<p>Yes. Unless you have to be compatible with Internet Explorer, Mutative supports almost all of Immer features, and you can easily migrate from Immer to Mutative.</p>\n<blockquote>\n<p>Migration is also not possible for React Native that does not support Proxy. React Native uses a new JS engine during refactoring - Hermes, and it (if &lt; v0.59 or when using the Hermes engine on React Native &lt; v0.64) does <a target=\"_blank\" href=\"https://github.com/facebook/hermes/issues/33\">not support Proxy on Android</a>, but <a target=\"_blank\" href=\"https://reactnative.dev/blog/2021/03/12/version-0.64#hermes-with-proxy-support\">React Native v0.64 with the Hermes engine support Proxy</a>.</p>\n</blockquote>\n<ul>\n<li>Can Mutative be integrated with Redux?</li>\n</ul>\n<p>Yes. Mutative supports return values for reducer, and <code>redux-toolkit</code> is considering support for <a target=\"_blank\" href=\"https://github.com/reduxjs/redux-toolkit/pull/3074\">configurable <code>produce()</code></a>.</p>\n<ul>\n<li>Which array methods run natively on drafts?</li>\n</ul>\n<p><code>shift</code>, <code>unshift</code>, <code>splice</code> and <code>reverse</code> move elements directly on the copy of a plain array without holes, including <code>undefined</code> elements; <code>indexOf</code>, <code>lastIndexOf</code> and <code>includes</code> search the current array natively; <code>sort</code> and <code>join</code> run natively on arrays of primitives. Removed elements are returned as drafts, patches replay in both directions, and a call that changes nothing keeps the state. Sparse arrays, array subclasses, an own <code>constructor</code> or <code>Symbol.isConcatSpreadable</code>, arrays under a custom <code>mark</code>, every method with a callback, <code>at</code>, <code>slice</code>, <code>fill</code> and <code>copyWithin</code> use the proxy path. An argument that can run user code, such as an object passed as <code>fromIndex</code> or as a <code>splice</code> index, is converted on the proxy path, so a conversion that changes the array is observed as the native methods observe it. The fast paths are made for data arrays: an accessor property defined on an array index is read as a data value, and the number and order of such getter calls, their re-entrant effects on the draft, and the identity of objects they return are not guaranteed to match element-by-element execution through the proxy.</p>\n<p>Optimized searches give the same results as the proxy path, comparing elements as a read returns them. Drafts, values assigned in the recipe, non-draftable objects and primitives are found. An object of the base state is drafted when it is read, so it is never found, whether or not it was read before; use <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#original\"><code>original()</code></a> to search the base state, for example <code>original(draft.list).indexOf(item)</code>. The search never reads a property of the value it is given.</p>\n<p>In strict mode, outside <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#unsafe\"><code>unsafe()</code></a>, optimized calls on an array that may hold objects take the proxy path unchanged, so reading a non-draftable element fails exactly as it always did; arrays of primitives, recognized with <code>typeof</code> alone, keep the native paths. Elements are moved and compared without being inspected, while the proxy path inspects each element it reads. An element that is itself a Proxy may therefore see fewer calls to its internal methods on the native paths, never more and never at other times; a revoked Proxy element that a search passes over, for example, does not throw there.</p>\n<p>Draftable base elements removed or moved by these methods are drafted before they are exposed. Methods with callbacks, such as <code>forEach</code>, <code>map</code>, <code>filter</code> and <code>find</code>, go through the draft so that their callbacks see every change and can modify elements; use <a target=\"_blank\" href=\"https://github.com/unadlib/mutative#current\"><code>current()</code></a> for read-only scans of large arrays.</p>\n<ul>\n<li>Does Mutative support shared references?</li>\n</ul>\n<p>Yes, Mutative supports shared references, but <strong>each path to a shared object gets its own independent draft</strong>. Modifications to one path do not automatically reflect in others. If you want to preserve shared references in the result, you must explicitly assign them (e.g., <code>draft.b = draft.a</code>). <a target=\"_blank\" href=\"https://mutative.js.org/docs/extra-topics/shared-references\">Read more details</a>.</p>\n<p></p><h2>Migration from Immer to Mutative</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#migration-from-immer-to-mutative\"></a><p></p>\n<blockquote>\n<p><a target=\"_blank\" href=\"https://github.com/exuanbo/mutative-compat\">mutative-compat</a> - Mutative wrapper with full Immer API compatibility, you can use it to quickly migrate from Immer to Mutative.</p>\n</blockquote>\n<ol>\n<li><code>produce()</code> -&gt; <code>create()</code></li>\n</ol>\n<p>Mutative auto freezing option is disabled by default, Immer auto freezing option is enabled by default.</p>\n<blockquote>\n<p>You need to check if auto freezing has any impact on your project. If it depends on auto freezing, you can enable it yourself in Mutative.</p>\n</blockquote>\n<div><pre><span>import</span> <span>produce</span> <span>from</span> <span>\"immer\"</span><span>;</span>\n<span>const</span> <span>nextState</span> <span>=</span> <span>produce</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>[</span><span>1</span><span>]</span><span>.</span><span>done</span> <span>=</span> <span>true</span><span>;</span>\n <span>draft</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>title</span>: <span>\"something\"</span> <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p>Use Mutative</p>\n<div><pre><span>import</span> <span>{</span> <span>create</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>nextState</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>[</span><span>1</span><span>]</span><span>.</span><span>done</span> <span>=</span> <span>true</span><span>;</span>\n <span>draft</span><span>.</span><span>push</span><span>(</span><span>{</span> <span>title</span>: <span>\"something\"</span> <span>}</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<ol>\n<li><code>Patches</code></li>\n</ol>\n<div><pre><span>import</span> <span>{</span> <span>produceWithPatches</span><span>,</span> <span>applyPatches</span> <span>}</span> <span>from</span> <span>\"immer\"</span><span>;</span>\n<span>enablePatches</span><span>(</span><span>)</span><span>;</span>\n<span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>age</span>: <span>33</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>[</span><span>nextState</span><span>,</span> <span>patches</span><span>,</span> <span>inversePatches</span><span>]</span> <span>=</span> <span>produceWithPatches</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>age</span><span>++</span><span>;</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>applyPatches</span><span>(</span><span>nextState</span><span>,</span> <span>inversePatches</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>baseState</span><span>)</span><span>;</span></pre></div>\n<p>Use Mutative</p>\n<div><pre><span>import</span> <span>{</span> <span>create</span><span>,</span> <span>apply</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>baseState</span> <span>=</span> <span>{</span>\n <span>age</span>: <span>33</span><span>,</span>\n<span>}</span><span>;</span>\n<span>const</span> <span>[</span><span>nextState</span><span>,</span> <span>patches</span><span>,</span> <span>inversePatches</span><span>]</span> <span>=</span> <span>create</span><span>(</span>\n <span>baseState</span><span>,</span>\n <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>draft</span><span>.</span><span>age</span><span>++</span><span>;</span>\n <span>}</span><span>,</span>\n <span>{</span>\n <span>enablePatches</span>: <span>true</span><span>,</span>\n <span>}</span><span>,</span>\n<span>)</span><span>;</span>\n<span>const</span> <span>state</span> <span>=</span> <span>apply</span><span>(</span><span>nextState</span><span>,</span> <span>inversePatches</span><span>)</span><span>;</span>\n<span>expect</span><span>(</span><span>state</span><span>)</span><span>.</span><span>toEqual</span><span>(</span><span>baseState</span><span>)</span><span>;</span></pre></div>\n<ol>\n<li>Return <code>undefined</code></li>\n</ol>\n<div><pre><span>import</span> <span>produce</span><span>,</span> <span>{</span> <span>nothing</span> <span>}</span> <span>from</span> <span>\"immer\"</span><span>;</span>\n<span>const</span> <span>nextState</span> <span>=</span> <span>produce</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>return</span> <span>nothing</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p>Use Mutative</p>\n<div><pre><span>import</span> <span>{</span> <span>create</span><span>,</span> <span>rawReturn</span> <span>}</span> <span>from</span> <span>\"mutative\"</span><span>;</span>\n<span>const</span> <span>nextState</span> <span>=</span> <span>create</span><span>(</span><span>baseState</span><span>,</span> <span>(</span><span>draft</span><span>)</span> <span>=&gt;</span> <span>{</span>\n <span>return</span> <span>rawReturn</span><span>(</span><span>undefined</span><span>)</span><span>;</span>\n<span>}</span><span>)</span><span>;</span></pre></div>\n<p></p><h2>Contributing</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#contributing\"></a><p></p>\n<p>Mutative goal is to provide efficient and immutable updates. The focus is on performance improvements and providing better APIs for better development experiences. We are still working on it and welcome PRs that may help Mutative.</p>\n<p>Development Workflow:</p>\n<p>See <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/BUILDING.md\">Building and validating Mutative</a> for the build pipeline, package checks, and bundle-size regression policy.</p>\n<p>See <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/perf-testing/README.md\">the benchmark suite</a> for the comparison of the current build with Mutative 1.3.0, Immer, and a hand-written reducer, matched freeze and patch modes, memory measurements, and CI regression budgets. Run <code>pnpm benchmark:immer:check</code> to validate every workload and <code>pnpm benchmark:immer</code> to measure them.</p>\n<ul>\n<li>Clone Mutative repo.</li>\n<li>Run <code>pnpm install</code> to install all the dependencies.</li>\n<li>Run <code>pnpm format</code> to format the code.</li>\n<li><code>pnpm test --watch</code> runs an interactive test watcher.</li>\n<li>Run <code>pnpm commit</code> to make a git commit.</li>\n</ul>\n<p></p><h2>License</h2><a target=\"_blank\" href=\"https://github.com/unadlib/mutative#license\"></a><p></p>\n<p>Mutative is <a target=\"_blank\" href=\"https://github.com/unadlib/mutative/blob/main/LICENSE\">MIT licensed</a>.</p>\n</article></div>",
"author": "",
"favicon": "https://github.githubassets.com/favicons/favicon.svg",
"source": "github.com",
"published": "",
"ttr": 842,
"type": "object"
}