import type { Delta } from "./types" ; type Context = { left : any ; delta : Delta ; children ? : Array < Context > ; result ? : unknown ; name ? : string | number ; nested ? : boolean ; stopped : boolean ; } ; export function patch < TLeft extends any > ( params ) { const context = { left : params . left , delta : params . delta , children : undefined , name : undefined , nested : false , stopped : false , } ; function process ( context ) { const steps = [ nested_collectChildrenPatchFilter , array_collectChildrenPatchFilter , trivial_patchFilter , nested_patchFilter , array_patchFilter , ] ; for ( const step of steps ) { step ( context ) ; if ( context . stopped ) { context . stopped = false ; break ; } } if ( context . children ) { for ( const childrenContext of context . children ) { process ( childrenContext ) ; context . result = context . result ?? context . left ; if ( "result" in childrenContext === false ) { delete ( context . result as object ) [ childrenContext . name ! ] ; } else { ( context . result as object ) [ childrenContext . name ! ] = childrenContext . result ; } } } } process ( context ) ; return context . result as TLeft ; } function nested_collectChildrenPatchFilter ( context ) { if ( ! context || ! context . children ) { return ; } if ( context . delta . _t ) { return ; } for ( let child of context . children ) { if ( Object . prototype . hasOwnProperty . call ( context . left , child . name ! ) && child . result === undefined ) { delete context . left [ child . name ! ] ; } else if ( context . left [ child . name ! ] !== child . result ) { context . left [ child . name ! ] = child . result ; } } context . result = context . left ; context . stopped = true ; } function array_collectChildrenPatchFilter ( context ) { if ( ! context || ! context . children ) { return ; } if ( context . delta . _t !== "a" ) { return ; } let length = context . children . length ; let child ; for ( let index = 0 ; index < length ; index ++ ) { child = context . children [ index ] ; context . left [ child . name ! ] = child . result ; } context . result = context . left ; context . stopped = true ; } function trivial_patchFilter ( context ) { if ( typeof context . delta === "undefined" ) { context . result = context . left ; return ; } context . nested = ! Array . isArray ( context . delta ) ; if ( context . nested ) { return ; } if ( context . delta . length === 1 ) { context . result = context . delta [ 0 ] ; context . stopped = true ; return ; } if ( context . delta . length === 2 ) { context . result = context . delta [ 1 ] ; context . stopped = true ; return ; } if ( context . delta . length === 3 && context . delta [ 2 ] === 0 ) { context . stopped = true ; } } function nested_patchFilter ( context ) { if ( ! context . nested ) { return ; } if ( context . delta . _t ) { return ; } for ( let name in context . delta ) { if ( context . children === undefined ) { context . children = [ ] ; } context . children . push ( { left : context . left [ name ] , delta : context . delta [ name ] , name , stopped : false , } ) ; } context . stopped = true ; } const ARRAY_MOVE = 3 ; let compare = { numerically ( a , b ) { return a - b ; } , numericallyBy ( name ) { return ( a , b ) => a [ name ] - b [ name ] ; } , } ; function array_patchFilter ( context ) { if ( ! context . nested ) { return ; } if ( context . delta . _t !== "a" ) { return ; } let index ; let index1 ; let delta = context . delta ; let array = context . left ; let toRemove = [ ] ; let toInsert = [ ] ; let toModify = [ ] ; for ( index in delta ) { if ( index !== "_t" ) { if ( index [ 0 ] === "_" ) { if ( delta [ index ] [ 2 ] === 0 || delta [ index ] [ 2 ] === ARRAY_MOVE ) { toRemove . push ( parseInt ( index . slice ( 1 ) , 10 ) ) ; } else { throw new Error ( `only removal or move can be applied at original array indices,` + ` invalid diff type: ${ delta [ index ] [ 2 ] }` ) ; } } else { if ( delta [ index ] . length === 1 ) { toInsert . push ( { index : parseInt ( index , 10 ) , value : delta [ index ] [ 0 ] , } ) ; } else { toModify . push ( { index : parseInt ( index , 10 ) , delta : delta [ index ] , } ) ; } } } } toRemove = toRemove . sort ( compare . numerically ) ; for ( index = toRemove . length - 1 ; index >= 0 ; index -- ) { index1 = toRemove [ index ] ; let indexDiff = delta [ `_${ index1 }` ] ; let removedValue = array . splice ( index1 , 1 ) [ 0 ] ; if ( indexDiff [ 2 ] === ARRAY_MOVE ) { toInsert . push ( { index : indexDiff [ 1 ] , value : removedValue , } ) ; } } toInsert = toInsert . sort ( compare . numericallyBy ( "index" ) ) ; let toInsertLength = toInsert . length ; for ( index = 0 ; index < toInsertLength ; index ++ ) { let insertion = toInsert [ index ] ; array . splice ( insertion . index , 0 , insertion . value ) ; } let toModifyLength = toModify . length ; if ( toModifyLength > 0 ) { for ( index = 0 ; index < toModifyLength ; index ++ ) { let modification = toModify [ index ] ; if ( context . children === undefined ) { context . children = [ ] ; } context . children . push ( { left : context . left [ modification . index ] , delta : modification . delta , name : modification . index , stopped : false , } ) ; } } if ( ! context . children ) { context . result = context . left ; context . stopped = true ; return ; } }