whole_func_string: function changeOnPath(local, op, edit, syncId, editId){
  	_.assertInt(op)
  	//console.log('path: ' + JSON.stringify(path))
  	//_.assertArray(path)
  	
  	_.assertInt(editId)
  	
  	/*
  	We need:
  	- an index of every existing inner object by id
  	*/
  	
  	//console.log('GAR: ' + editNames[op] + ' ' + JSON.stringify(edit))
  	
  	//console.log('inputObject: ' + local.inputObject)
  	//console.log(JSON.stringify(local.edits))
  	/*local.edits.forEach(function(e, index){
  		console.log(index + ' ' + editNames[e.op] + ' ' + JSON.stringify(e))
  	})*/
  	//console.log('self: ' + local.id())
  	_.assertInt(local.inputProperty)
  
  	//console.log('own: ' + local.objectId + ' ' + local.inputObject + ' ')// + local._forkedObject._internalId())
  	
  	var ch;
  	if(!local.inputObject || local.inputObject === local.objectId || local.inputObject === -1){//TODO handle case where inputObject is the forked object
  		ch = local
  	}else if(local.lastSelf === local.inputObject){
  		ch = local
  	}else{
  		/*if(editId > 0 && editId !== local.getEditingId() && local.inputObject < 0){
  			_.errout('invalid inputObject: ' + local.inputObject + '\t' + editId + ' !== ' + local.getEditingId())
  		}*/
  		
  		if(local.objectApiCache){
  			ch = local.objectApiCache[local.inputObject]
  		}
  		
  		/*if(local.inputObject < 0){
  			var v = local.parent.temporaryCache[local.inputObject]
  			if(v){
  				ch = v.n
  				//console.log('looked up temporary ' + local.inputObject + ' got ' + Object.keys(ch))
  			}
  		}*/
  		/*if(local.inputObject < 0){
  			ch = local.temporary
  		}*/
  		if(!ch){
  			console.log('WARNING: not found ' + local.inputObject + ', only got: ' + JSON.stringify(Object.keys(local.objectApiCache||{})) + ' in ' + local.objectId + ' --- ' + editId)
  			console.log(JSON.stringify(local.edits, null, 2))
  			throw new Error('here')
  			return
  		}
  		_.assertObject(ch)
  	}
  	
  	var property = ch.typeSchema.propertiesByCode[local.inputProperty]
  	if(property === undefined){
  		var candidates = ' ' + JSON.stringify(_.map(Object.keys(ch.typeSchema.propertiesByCode),function(p){
  			return ch.typeSchema.propertiesByCode[p].name + ':'+ch.typeSchema.propertiesByCode[p].code}))
  		if(TopObjectHandle.prototype.errorOnWarn){
  			_.errout('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
  		}else{
  			console.log('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
  		}
  		return
  	}	
  
  	//console.log('modifying property: ' + property.name)
  	
  	if(op === editCodes.setObject || op === editCodes.setViewObject){
  		//_.assertInt(descentCode)
  		//var ps = this.typeSchema.propertiesByCode[descentCode];
  
  		//_.assertObject(ps)
  		if(op === editCodes.setObject){
  			if(property.type.type !== 'object'){
  				console.log(JSON.stringify(local.edits, null, 2))
  				_.errout('(' + editId + ') setObject called on non object type, type is: ' + JSON.stringify(property.type))
  			}
  			//_.assertEqual(ps.type.type, 'object')
  		}
  
  		var subj
  		if(local.inputObject === local.objectId || local.inputObject === local.lastSelf){
  			subj = local
  		}else{
  			subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
  		}
  
  		var pv = subj[property.name]//.cachedProperties[ps.name]
  		if(pv && pv.objectId === edit.id){
  			//already done
  			//console.log('setObject redundant (local view object?), skipping: ' + pv.objectId + ' ' + edit.id)
  		}else{
  			//this.log('set to: ' + edit.id + ' ' + descentCode + ' ' + this.objectId + ' ' + ps.name)
  			var setObj = local.getObjectApi(edit.id)
  			if(setObj === undefined){
  				local.log.warn('object not found, may have been del\'ed: ' + edit.id)
  				local.log(new Error().stack)
  				return
  			}
  			
  			if(setObj.isDestroyed()) return
  
  			//subj.obj[local.inputProperty] = setObj;
  			setObj.prepare()
  			subj[property.name] = setObj
  			//console.log('EMITTING SET OBJECT ' + property.name)
  			subj.emit(edit, 'set', property.name, setObj)			
  		}
  	}else if(op === editCodes.clearObject || op === editCodes.clearProperty){
  
  		var subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
  
  		var ps = subj.typeSchema.propertiesByCode[local.inputProperty];
  		
  		var chp = ch.property(ps.name)
  		
  		if(ps.type.type === 'set' || ps.type.type === 'list'){
  			chp.obj = []
  		}else if(ps.type.type === 'map'){
  			chp.obj = {}
  		}else{
  			chp.obj = undefined
  		}
  		//subj[ps.name] = undefined
  		//subj.obj[local.inputProperty] = undefined
  	}else{
  		
  		//console.log(chp.constructor+'')
  		var chp = ch.property(property.name)//[property.name]
  
  		_.assertEqual(ch[property.name], chp)
  		
  		if(!chp){
  			_.errout('property value missing: ' + property.name)
  		}
  		
  		if(!chp.changeListener){
  			//console.dir(chp)
  			_.errout('cannot determine current edit target(' + property.name+' in ' + ch.typeSchema.name + ' ' + ch.prepared + ' - ' + chp.constructor + ') failed to run edit: ' + JSON.stringify([editNames[op], edit, syncId, editId]))
  			return
  		}
  		//console.log('HERERERERER')
  		//console.log('editing ' + editNames[op] + ' ' + (chp.rally = Math.random()))
  		
  		//console.log(typeof(chp.changeListener))
  		
  		chp.changeListener(local.inputSubObject, local.inputKey, op, edit, syncId, editId)
  	}
  }
func_code_string: function changeOnPath(local, op, edit, syncId, editId){
  	_.assertInt(op)
  	//console.log('path: ' + JSON.stringify(path))
  	//_.assertArray(path)
  	
  	_.assertInt(editId)
  	
  	/*
  	We need:
  	- an index of every existing inner object by id
  	*/
  	
  	//console.log('GAR: ' + editNames[op] + ' ' + JSON.stringify(edit))
  	
  	//console.log('inputObject: ' + local.inputObject)
  	//console.log(JSON.stringify(local.edits))
  	/*local.edits.forEach(function(e, index){
  		console.log(index + ' ' + editNames[e.op] + ' ' + JSON.stringify(e))
  	})*/
  	//console.log('self: ' + local.id())
  	_.assertInt(local.inputProperty)
  
  	//console.log('own: ' + local.objectId + ' ' + local.inputObject + ' ')// + local._forkedObject._internalId())
  	
  	var ch;
  	if(!local.inputObject || local.inputObject === local.objectId || local.inputObject === -1){//TODO handle case where inputObject is the forked object
  		ch = local
  	}else if(local.lastSelf === local.inputObject){
  		ch = local
  	}else{
  		/*if(editId > 0 && editId !== local.getEditingId() && local.inputObject < 0){
  			_.errout('invalid inputObject: ' + local.inputObject + '\t' + editId + ' !== ' + local.getEditingId())
  		}*/
  		
  		if(local.objectApiCache){
  			ch = local.objectApiCache[local.inputObject]
  		}
  		
  		/*if(local.inputObject < 0){
  			var v = local.parent.temporaryCache[local.inputObject]
  			if(v){
  				ch = v.n
  				//console.log('looked up temporary ' + local.inputObject + ' got ' + Object.keys(ch))
  			}
  		}*/
  		/*if(local.inputObject < 0){
  			ch = local.temporary
  		}*/
  		if(!ch){
  			console.log('WARNING: not found ' + local.inputObject + ', only got: ' + JSON.stringify(Object.keys(local.objectApiCache||{})) + ' in ' + local.objectId + ' --- ' + editId)
  			console.log(JSON.stringify(local.edits, null, 2))
  			throw new Error('here')
  			return
  		}
  		_.assertObject(ch)
  	}
  	
  	var property = ch.typeSchema.propertiesByCode[local.inputProperty]
  	if(property === undefined){
  		var candidates = ' ' + JSON.stringify(_.map(Object.keys(ch.typeSchema.propertiesByCode),function(p){
  			return ch.typeSchema.propertiesByCode[p].name + ':'+ch.typeSchema.propertiesByCode[p].code}))
  		if(TopObjectHandle.prototype.errorOnWarn){
  			_.errout('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
  		}else{
  			console.log('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
  		}
  		return
  	}	
  
  	//console.log('modifying property: ' + property.name)
  	
  	if(op === editCodes.setObject || op === editCodes.setViewObject){
  		//_.assertInt(descentCode)
  		//var ps = this.typeSchema.propertiesByCode[descentCode];
  
  		//_.assertObject(ps)
  		if(op === editCodes.setObject){
  			if(property.type.type !== 'object'){
  				console.log(JSON.stringify(local.edits, null, 2))
  				_.errout('(' + editId + ') setObject called on non object type, type is: ' + JSON.stringify(property.type))
  			}
  			//_.assertEqual(ps.type.type, 'object')
  		}
  
  		var subj
  		if(local.inputObject === local.objectId || local.inputObject === local.lastSelf){
  			subj = local
  		}else{
  			subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
  		}
  
  		var pv = subj[property.name]//.cachedProperties[ps.name]
  		if(pv && pv.objectId === edit.id){
  			//already done
  			//console.log('setObject redundant (local view object?), skipping: ' + pv.objectId + ' ' + edit.id)
  		}else{
  			//this.log('set to: ' + edit.id + ' ' + descentCode + ' ' + this.objectId + ' ' + ps.name)
  			var setObj = local.getObjectApi(edit.id)
  			if(setObj === undefined){
  				local.log.warn('object not found, may have been del\'ed: ' + edit.id)
  				local.log(new Error().stack)
  				return
  			}
  			
  			if(setObj.isDestroyed()) return
  
  			//subj.obj[local.inputProperty] = setObj;
  			setObj.prepare()
  			subj[property.name] = setObj
  			//console.log('EMITTING SET OBJECT ' + property.name)
  			subj.emit(edit, 'set', property.name, setObj)			
  		}
  	}else if(op === editCodes.clearObject || op === editCodes.clearProperty){
  
  		var subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
  
  		var ps = subj.typeSchema.propertiesByCode[local.inputProperty];
  		
  		var chp = ch.property(ps.name)
  		
  		if(ps.type.type === 'set' || ps.type.type === 'list'){
  			chp.obj = []
  		}else if(ps.type.type === 'map'){
  			chp.obj = {}
  		}else{
  			chp.obj = undefined
  		}
  		//subj[ps.name] = undefined
  		//subj.obj[local.inputProperty] = undefined
  	}else{
  		
  		//console.log(chp.constructor+'')
  		var chp = ch.property(property.name)//[property.name]
  
  		_.assertEqual(ch[property.name], chp)
  		
  		if(!chp){
  			_.errout('property value missing: ' + property.name)
  		}
  		
  		if(!chp.changeListener){
  			//console.dir(chp)
  			_.errout('cannot determine current edit target(' + property.name+' in ' + ch.typeSchema.name + ' ' + ch.prepared + ' - ' + chp.constructor + ') failed to run edit: ' + JSON.stringify([editNames[op], edit, syncId, editId]))
  			return
  		}
  		//console.log('HERERERERER')
  		//console.log('editing ' + editNames[op] + ' ' + (chp.rally = Math.random()))
  		
  		//console.log(typeof(chp.changeListener))
  		
  		chp.changeListener(local.inputSubObject, local.inputKey, op, edit, syncId, editId)
  	}
  }
func_documentation_string: /*
  TopObjectHandle.prototype._acceptForkChange = function(subObj, key, op, edit, syncId, editId){//, path){
  
  console.log('MAINTAINING FORK: ' + op + ' ' + JSON.stringify(edit) + ' on ' + this.objectId + ' ' + syncId + ' ' + this.getEditingId())
  
  var manyToRemove = 0
  for(var i=0;i<this.edits.length;++i){
  var e = this.edits[i]
  if((e.op === editCodes.made || e.op === editCodes.madeFork) && e.edit.sourceId === this._forkedObject._internalId()){
  break;
  }
  ++manyToRemove
  }
  this.edits = this.edits.slice(manyToRemove)
  this.edits = [].concat(this._forkedObject._getEdits()).concat(this.edits)
  
  this._rebuild()
  }