@@ -15,10 +15,13 @@ import {
1515 getPlugin ,
1616 die ,
1717 revokeScope ,
18- isFrozen
18+ isFrozen ,
19+ type Objectish ,
20+ type Drafted ,
21+ prepareCopy
1922} from "../internal"
2023
21- export function processResult ( result : any , scope : ImmerScope ) {
24+ export function processResult ( result : any , scope : ImmerScope , existingStateMap ?: WeakMap < Objectish , ImmerState > , existingFinalizationMap ?: WeakMap < Objectish , Drafted > ) {
2225 scope . unfinalizedDrafts_ = scope . drafts_ . length
2326 const baseDraft = scope . drafts_ ! [ 0 ]
2427 const isReplaced = result !== undefined && result !== baseDraft
@@ -29,8 +32,8 @@ export function processResult(result: any, scope: ImmerScope) {
2932 }
3033 if ( isDraftable ( result ) ) {
3134 // Finalize the result in case it contains (or is) a subset of the draft.
32- result = finalize ( scope , result )
33- if ( ! scope . parent_ ) maybeFreeze ( scope , result )
35+ result = finalize ( scope , result , undefined , existingStateMap , existingFinalizationMap )
36+ if ( ! scope . parent_ ) maybeFreeze ( scope , result , false )
3437 }
3538 if ( scope . patches_ ) {
3639 getPlugin ( "Patches" ) . generateReplacementPatches_ (
@@ -42,7 +45,7 @@ export function processResult(result: any, scope: ImmerScope) {
4245 }
4346 } else {
4447 // Finalize the base draft.
45- result = finalize ( scope , baseDraft , [ ] )
48+ result = finalize ( scope , baseDraft , [ ] , existingStateMap , existingFinalizationMap )
4649 }
4750 revokeScope ( scope )
4851 if ( scope . patches_ ) {
@@ -51,17 +54,19 @@ export function processResult(result: any, scope: ImmerScope) {
5154 return result !== NOTHING ? result : undefined
5255}
5356
54- function finalize ( rootScope : ImmerScope , value : any , path ?: PatchPath ) {
57+ function finalize ( rootScope : ImmerScope , value : any , path ?: PatchPath , existingStateMap ?: WeakMap < Objectish , ImmerState > , existingFinalizationMap ?: WeakMap < Objectish , Drafted > ) : any {
5558 // Don't recurse in tho recursive data structures
5659 if ( isFrozen ( value ) ) return value
5760
58- const state : ImmerState = value [ DRAFT_STATE ]
61+ let state : ImmerState = value [ DRAFT_STATE ]
62+
5963 // A plain object, might need freezing, might contain drafts
6064 if ( ! state ) {
65+ existingFinalizationMap ?. set ( value , value )
6166 each (
6267 value ,
6368 ( key , childValue ) =>
64- finalizeProperty ( rootScope , state , value , key , childValue , path ) ,
69+ finalizeProperty ( rootScope , state , value , key , childValue , path , undefined , existingStateMap , existingFinalizationMap ) ,
6570 true // See #590, don't recurse into non-enumerable of non drafted objects
6671 )
6772 return value
@@ -70,11 +75,13 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
7075 if ( state . scope_ !== rootScope ) return value
7176 // Unmodified draft, return the (frozen) original
7277 if ( ! state . modified_ ) {
73- maybeFreeze ( rootScope , state . base_ , true )
78+ maybeFreeze ( rootScope , state . copy_ ?? state . base_ , true ) ;
7479 return state . base_
7580 }
7681 // Not finalized yet, let's do that now
7782 if ( ! state . finalized_ ) {
83+ existingFinalizationMap ?. set ( state . base_ , state . copy_ )
84+
7885 state . finalized_ = true
7986 state . scope_ . unfinalizedDrafts_ --
8087 const result = state . copy_
@@ -90,7 +97,7 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
9097 isSet = true
9198 }
9299 each ( resultEach , ( key , childValue ) =>
93- finalizeProperty ( rootScope , state , result , key , childValue , path , isSet )
100+ finalizeProperty ( rootScope , state , result , key , childValue , path , isSet , existingStateMap , existingFinalizationMap )
94101 )
95102 // everything inside is frozen, we can freeze here
96103 maybeFreeze ( rootScope , result , false )
@@ -104,6 +111,7 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
104111 )
105112 }
106113 }
114+
107115 return state . copy_
108116}
109117
@@ -114,10 +122,23 @@ function finalizeProperty(
114122 prop : string | number ,
115123 childValue : any ,
116124 rootPath ?: PatchPath ,
117- targetIsSet ?: boolean
125+ targetIsSet ?: boolean ,
126+ existingStateMap ?: WeakMap < Objectish , ImmerState > ,
127+ existingFinalizationMap ?: WeakMap < Objectish , Drafted > ,
118128) {
119129 if ( process . env . NODE_ENV !== "production" && childValue === targetObject )
120130 die ( 5 )
131+
132+ if ( ! isDraft ( childValue ) && isDraftable ( childValue ) ) {
133+ const existingState = existingStateMap ?. get ( childValue )
134+ if ( existingState ) {
135+ childValue = existingState . draft_
136+ } else {
137+ const existingFinalization = existingFinalizationMap ?. get ( childValue )
138+ if ( existingFinalization ) return set ( targetObject , prop , existingFinalization )
139+ }
140+ }
141+
121142 if ( isDraft ( childValue ) ) {
122143 const path =
123144 rootPath &&
@@ -127,7 +148,7 @@ function finalizeProperty(
127148 ? rootPath ! . concat ( prop )
128149 : undefined
129150 // Drafts owned by `scope` are finalized here.
130- const res = finalize ( rootScope , childValue , path )
151+ const res = finalize ( rootScope , childValue , path , existingStateMap , existingFinalizationMap )
131152 set ( targetObject , prop , res )
132153 // Drafts from another scope must prevented to be frozen
133154 // if we got a draft back from finalize, we're in a nested produce and shouldn't freeze
@@ -137,6 +158,7 @@ function finalizeProperty(
137158 } else if ( targetIsSet ) {
138159 targetObject . add ( childValue )
139160 }
161+
140162 // Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
141163 if ( isDraftable ( childValue ) && ! isFrozen ( childValue ) ) {
142164 if ( ! rootScope . immer_ . autoFreeze_ && rootScope . unfinalizedDrafts_ < 1 ) {
@@ -147,10 +169,10 @@ function finalizeProperty(
147169 // See add-data.js perf test
148170 return
149171 }
150- finalize ( rootScope , childValue )
172+ finalize ( rootScope , childValue , undefined , existingStateMap , existingFinalizationMap )
151173 // immer deep freezes plain objects, so if there is no parent state, we freeze as well
152174 if ( ! parentState || ! parentState . scope_ . parent_ )
153- maybeFreeze ( rootScope , childValue )
175+ maybeFreeze ( rootScope , childValue , false ) ;
154176 }
155177}
156178
0 commit comments