Nested create() on draft element shares references with original base object
## Description
When calling `create()` on an object that is already a Mutative draft proxy, the resulting object's nested properties that weren't modified in the recipe share references with the **original base object**, not the draft's current state.
This allows accidental mutation of the original object, which violates immutability expectations.
## Minimal Reproduction
```typescript
import { create } from 'mutative';
interface Metadata {
value: string;
}
interface Node {
name: string;
metadata: Metadata;
}
interface Tree {
nodes: Node[];
}
// Helper function that uses create() internally - common pattern for reusable transformations
function updateName(node: Node, newName: string): Node {
return create(node, (draft) => {
draft.name = newName;
});
}
const original: Tree = {
nodes: [
{ name: 'a', metadata: { value: '' } }
]
};
const result = create(original, (draft) => {
draft.nodes = draft.nodes.map((node) => {
// Calling a helper that uses create() - doesn't look like nested create()
const updated = updateName(node, 'modified');
// BUG: updated.metadata shares reference with original
updated.metadata.value = 'oops';
return updated;
});
});
console.log('Original mutated?', original.nodes[0].metadata.value === 'oops'); // true!
```
Note: In real code, the nested `create()` call is typically hidden inside a helper function (like `updateName` above), making it non-obvious that you're calling `create()` on a draft element.
## Expected Behavior
When `create()` is called on a draft element, the resulting object should not share mutable references with the original base object. Either:
1. Nested `create()` should work correctly (like Immer handles nested `produce()`)
2. Or an error/warning should be thrown indicating this pattern isn't supported
## Actual Behavior
The object returned from the inner `create()` call has nested properties (`metadata`) that reference the original base object. Mutating these nested properties unexpectedly mutates the original.
## Environment
- mutative version: latest
- Node.js version: v20+
## Context
We discovered this while migrating from Immer to Mutative. Our codebase uses a visitor pattern where helper functions call `create()` to modify parts of a tree, and these helpers are sometimes invoked from within a `create()` recipe. This pattern worked correctly with Immer but causes original tree mutation with Mutative.
1 条评论