@@ -29,6 +29,13 @@ export type TravelsOptions<F extends boolean, A extends boolean> = {
2929 * Whether to automatically archive the current state, by default `true`
3030 */
3131 autoArchive ?: A ;
32+ /**
33+ * Whether to mutate the state in place (for observable state like MobX, Vue, Pinia)
34+ * When true, apply patches directly to the existing state object
35+ * When false (default), create new immutable state objects
36+ * @default false
37+ */
38+ mutable ?: boolean ;
3239} & Omit < MutativeOptions < true , F > , 'enablePatches' > ;
3340
3441type InitialValue < I extends any > = I extends ( ...args : any ) => infer R ? R : I ;
@@ -112,6 +119,7 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
112119 private initialPosition : number ;
113120 private initialPatches ?: TravelPatches ;
114121 private autoArchive : A ;
122+ private mutable : boolean ;
115123 private options : Omit < MutativeOptions < true , F > , 'enablePatches' > ;
116124 private listeners : Set < Listener < S > > = new Set ( ) ;
117125 private pendingState : S | null = null ;
@@ -122,6 +130,7 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
122130 initialPatches,
123131 initialPosition = 0 ,
124132 autoArchive = true as A ,
133+ mutable = false ,
125134 ...mutativeOptions
126135 } = options ;
127136
@@ -158,12 +167,16 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
158167 }
159168
160169 this . state = initialState ;
161- this . initialState = initialState ;
170+ // For mutable mode, deep clone initialState to prevent mutations
171+ this . initialState = mutable
172+ ? JSON . parse ( JSON . stringify ( initialState ) )
173+ : initialState ;
162174 this . position = initialPosition ;
163175 this . initialPosition = initialPosition ;
164176 this . maxHistory = maxHistory ;
165177 this . initialPatches = initialPatches ;
166178 this . autoArchive = autoArchive ;
179+ this . mutable = mutable ;
167180 this . options = mutativeOptions ;
168181 this . allPatches = cloneTravelPatches ( initialPatches ) ;
169182 this . tempPatches = cloneTravelPatches ( ) ;
@@ -200,20 +213,51 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
200213 * Update the state
201214 */
202215 public setState ( updater : Updater < S > ) : void {
203- const [ nextState , patches , inversePatches ] = (
204- typeof updater === 'function'
205- ? create ( this . state , updater as any , {
206- ...this . options ,
207- enablePatches : true ,
208- } )
209- : create ( this . state , ( ) => updater , {
210- ...this . options ,
211- enablePatches : true ,
212- } )
213- ) as [ S , Patches < true > , Patches < true > ] ;
214-
215- this . state = nextState ;
216- this . pendingState = nextState ;
216+ let patches : Patches < true > ;
217+ let inversePatches : Patches < true > ;
218+
219+ if ( this . mutable ) {
220+ // For observable state: generate patches then apply mutably
221+ const isFn = typeof updater === 'function' ;
222+
223+ [ , patches , inversePatches ] = create (
224+ this . state ,
225+ isFn
226+ ? ( updater as any )
227+ : ( draft : any ) => {
228+ // For non-function updater, assign all properties to draft
229+ Object . assign ( draft , updater ) ;
230+ } ,
231+ {
232+ ...this . options ,
233+ enablePatches : true ,
234+ }
235+ ) as [ S , Patches < true > , Patches < true > ] ;
236+
237+ // Apply patches to mutate the existing state object
238+ apply ( this . state as object , patches , { mutable : true } ) ;
239+
240+ // Keep the same reference
241+ this . pendingState = this . state ;
242+ } else {
243+ // For immutable state: create new object
244+ const [ nextState , p , ip ] = (
245+ typeof updater === 'function'
246+ ? create ( this . state , updater as any , {
247+ ...this . options ,
248+ enablePatches : true ,
249+ } )
250+ : create ( this . state , ( ) => updater , {
251+ ...this . options ,
252+ enablePatches : true ,
253+ } )
254+ ) as [ S , Patches < true > , Patches < true > ] ;
255+
256+ patches = p ;
257+ inversePatches = ip ;
258+ this . state = nextState ;
259+ this . pendingState = nextState ;
260+ }
217261
218262 // Reset pendingState asynchronously
219263 Promise . resolve ( ) . then ( ( ) => {
@@ -419,19 +463,24 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
419463 ] . reverse ( ) ;
420464 }
421465
422- this . state = apply (
423- this . state as object ,
424- back
425- ? _allPatches . inversePatches
426- . slice ( - this . maxHistory )
427- . slice ( nextPosition )
428- . flat ( )
429- . reverse ( )
430- : _allPatches . patches
431- . slice ( - this . maxHistory )
432- . slice ( this . position , nextPosition )
433- . flat ( )
434- ) as S ;
466+ const patchesToApply = back
467+ ? _allPatches . inversePatches
468+ . slice ( - this . maxHistory )
469+ . slice ( nextPosition , this . position )
470+ . flat ( )
471+ . reverse ( )
472+ : _allPatches . patches
473+ . slice ( - this . maxHistory )
474+ . slice ( this . position , nextPosition )
475+ . flat ( ) ;
476+
477+ if ( this . mutable ) {
478+ // For observable state: mutate in place
479+ apply ( this . state as object , patchesToApply , { mutable : true } ) ;
480+ } else {
481+ // For immutable state: create new object
482+ this . state = apply ( this . state as object , patchesToApply ) as S ;
483+ }
435484
436485 this . position = nextPosition ;
437486 this . notify ( ) ;
@@ -457,8 +506,30 @@ export class Travels<S, F extends boolean = false, A extends boolean = true> {
457506 public reset ( ) : void {
458507 this . position = this . initialPosition ;
459508 this . allPatches = cloneTravelPatches ( this . initialPatches ) ;
460- this . state = this . initialState ;
461509 this . tempPatches = cloneTravelPatches ( ) ;
510+
511+ if ( this . mutable ) {
512+ // For observable state: mutate back to initial state
513+ // Directly mutate each property to match initial state
514+ const state = this . state as any ;
515+ const initial = this . initialState as any ;
516+
517+ // Remove properties that exist in current but not in initial
518+ for ( const key of Object . keys ( state ) ) {
519+ if ( ! ( key in initial ) ) {
520+ delete state [ key ] ;
521+ }
522+ }
523+
524+ // Set/update all properties from initial state
525+ for ( const key of Object . keys ( initial ) ) {
526+ state [ key ] = initial [ key ] ;
527+ }
528+ } else {
529+ // For immutable state: reassign reference
530+ this . state = this . initialState ;
531+ }
532+
462533 this . notify ( ) ;
463534 }
464535
0 commit comments