text: // fork by : couralex/universal-observer 
  // https://github.com/couralex/universal-observer#readme
  const
  	INSERT = 'insert',
  	UPDATE = 'update',
  	DELETE = 'delete',
  	REVERSE = 'reverse',
  	SHUFFLE = 'shuffle',
  	sysObsKey = Symbol('system-observer-key'),
  	nonObservables = {
  		Date: true,
  		Blob: true,
  		Number: true,
  		String: true,
  		Boolean: true,
  		Error: true,
  		SyntaxError: true,
  		TypeError: true,
  		URIError: true,
  		Function: true,
  		Promise: true,
  		RegExp: true
  	},
  	observableDefinition = {
  		revoke: {
  			value: function() {
  				this[sysObsKey].revoke();
  			}
  		},
  		observe: {
  			value: function(observer) {
  				let systemObserver = this[sysObsKey],
  					observers = systemObserver.observers;
  				if (systemObserver.isRevoked) { throw new TypeError('revoked Observable MAY NOT be observed anymore'); }
  				if (typeof observer !== 'function') { throw new Error('observer parameter MUST be a function'); }
  
  				if (observers.indexOf(observer) < 0) {
  					observers.push(observer);
  				} else {
  					console.info('observer may be bound to an observable only once');
  				}
  			}
  		},
  		unobserve: {
  			value: function() {
  				let systemObserver = this[sysObsKey],
  					observers = systemObserver.observers,
  					l, idx;
  				if (systemObserver.isRevoked) { throw new TypeError('revoked Observable MAY NOT be unobserved anymore'); }
  				l = arguments.length;
  				if (l) {
  					while (l--) {
  						idx = observers.indexOf(arguments[l]);
  						if (idx >= 0) observers.splice(idx, 1);
  					}
  				} else {
  					observers.splice(0);
  				}
  			}
  		}
  	},
  	prepareArray = function(origin, destination, observer) {
  		let l = origin.length, item;
  		destination[sysObsKey] = observer;
  		while (l--) {
  			destination[l] = origin[l];
  			// item = origin[l];
  			// if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  			// 	destination[l] = Array.isArray(item)
  			// 		? new ArrayObserver({target: item, ownKey: l, parent: observer}).proxy
  			// 		: new ObjectObserver({target: item, ownKey: l, parent: observer}).proxy;
  			// } else {
  			// 	destination[l] = item;
  			// }
  		}
  	},
  	prepareObject = function(origin, destination, observer) {
  		let keys = Object.keys(origin), l = keys.length, key, item;
  		destination[sysObsKey] = observer;
  		while (l--) {
  			key = keys[l];
  			destination[key] = origin[key];
  			// item = origin[key];
  			// if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  			// 	destination[key] = Array.isArray(item)
  			// 		? new ArrayObserver({target: item, ownKey: key, parent: observer}).proxy
  			// 		: new ObjectObserver({target: item, ownKey: key, parent: observer}).proxy;
  			// } else {
  			// 	destination[key] = item;
  			// }
  		}
  	},
  	callObservers = async function(observers, changes) {
  		let l = observers.length;
  		while (l--) {
  			try {
  				await observers[l](changes);
  			} catch (e) {
  				console.error('failed to deliver changes to listener' + observers[l], e);
  			}
  		}
  	},
  	getAncestorInfo = function(self) {
  		let tmp = [], result, l1 = 0, l2 = 0;
  		while (self.parent) {
  			tmp[l1++] = self.ownKey;
  			self = self.parent;
  		}
  		result = new Array(l1);
  		while (l1--) result[l2++] = tmp[l1];
  		return {observers: self.observers, path: result};
  	};
  
  class ArrayObserver {
  	constructor(properties) {
  		let origin = properties.target, clone = new Array(origin.length);
  		if (properties.parent === null) {
  			this.isRevoked = false;
  			this.observers = [];
  			Object.defineProperties(clone, observableDefinition);
  		} else {
  			this.parent = properties.parent;
  			this.ownKey = properties.ownKey;
  		}
  		prepareArray(origin, clone, this);
  		this.revokable = Proxy.revocable(clone, this);
  		this.proxy = this.revokable.proxy;
  		this.target = clone;
  	}
  
  	//	returns an unobserved graph (effectively this is an opposite of an ArrayObserver constructor logic)
  	revoke() {
  		//	revoke native proxy
  		this.revokable.revoke();
  
  		//	roll back observed array to an unobserved one
  		let target = this.target, l = target.length, item;
  		while (l--) {
  			item = target[l];
  			if (item && typeof item === 'object') {
  				let tmpObserved = item[sysObsKey];
  				if (tmpObserved) {
  					target[l] = tmpObserved.revoke();
  				}
  			}
  		}
  		return target;
  	}
  
  	get(target, key) {
  		const proxiedArrayMethods = {
  			pop: async function proxiedPop(target, observed) {
  				let poppedIndex, popResult;
  				poppedIndex = target.length - 1;
  				popResult = target.pop();
  				if (popResult && typeof popResult === 'object') {
  					let tmpObserved = popResult[sysObsKey];
  					if (tmpObserved) {
  						popResult = tmpObserved.revoke();
  					}
  				}
  
  				//	publish changes
  				let ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					ad.path.push(poppedIndex);
  					await callObservers(ad.observers, [{type: DELETE, path: ad.path, oldValue: popResult}]);
  				}
  				return popResult;
  			},
  			push: async function proxiedPush(target, observed) {
  				let i, l = arguments.length - 2, item, pushContent = new Array(l), pushResult, changes,
  					initialLength, ad = getAncestorInfo(observed);
  				initialLength = target.length;
  
  				for (i = 0; i < l; i++) {
  					item = arguments[i + 2];
  					// if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  					// 	item = Array.isArray(item)
  					// 		? new ArrayObserver({target: item, ownKey: initialLength + i, parent: observed}).proxy
  					// 		: new ObjectObserver({target: item, ownKey: initialLength + i, parent: observed}).proxy;
  					// }
  					pushContent[i] = item;
  				}
  				pushResult = Reflect.apply(target.push, target, pushContent);
  
  				//	publish changes
  				if (ad.observers.length) {
  					changes = [];
  					for (i = initialLength, l = target.length; i < l; i++) {
  						let path = ad.path.slice(0);
  						path.push(i);
  						changes[i - initialLength] = {type: INSERT, path: path, value: target[i]};
  					}
  					await callObservers(ad.observers, changes);
  				}
  				return pushResult;
  			},
  			shift: async function proxiedShift(target, observed) {
  				let shiftResult, i, l, item, ad, changes;
  
  				shiftResult = target.shift();
  				if (shiftResult && typeof shiftResult === 'object') {
  					let tmpObserved = shiftResult[sysObsKey];
  					if (tmpObserved) {
  						shiftResult = tmpObserved.revoke();
  					}
  				}
  
  				//	update indices of the remaining items
  				for (i = 0, l = target.length; i < l; i++) {
  					item = target[i];
  					if (item && typeof item === 'object') {
  						let tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							tmpObserved.ownKey = i;
  						}
  					}
  				}
  
  				//	publish changes
  				ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					ad.path.push(0);
  					changes = [{type: DELETE, path: ad.path, oldValue: shiftResult}];
  					await callObservers(ad.observers, changes);
  				}
  				return shiftResult;
  			},
  			unshift: async function proxiedUnshift(target, observed) {
  				let unshiftContent, unshiftResult, ad, changes;
  				unshiftContent = Array.from(arguments);
  				unshiftContent.splice(0, 2);
  				// unshiftContent.forEach((item, index) => {
  				// 	if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  				// 		unshiftContent[index] = Array.isArray(item)
  				// 			? new ArrayObserver({target: item, ownKey: index, parent: observed}).proxy
  				// 			: new ObjectObserver({target: item, ownKey: index, parent: observed}).proxy;
  				// 	}
  				// });
  				unshiftResult = Reflect.apply(target.unshift, target, unshiftContent);
  				for (let i = 0, l = target.length, item; i < l; i++) {
  					item = target[i];
  					if (item && typeof item === 'object') {
  						let tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							tmpObserved.ownKey = i;
  						}
  					}
  				}
  
  				//	publish changes
  				ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					let l = unshiftContent.length, path;
  					changes = new Array(l);
  					for (let i = 0; i < l; i++) {
  						path = ad.path.slice(0);
  						path.push(i);
  						changes[i] = {type: INSERT, path: path, value: target[i]};
  					}
  					await callObservers(ad.observers, changes);
  				}
  				return unshiftResult;
  			},
  			reverse: async function proxiedReverse(target, observed) {
  				let i, l, item, ad, changes;
  				target.reverse();
  				for (i = 0, l = target.length; i < l; i++) {
  					item = target[i];
  					if (item && typeof item === 'object') {
  						let tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							tmpObserved.ownKey = i;
  						}
  					}
  				}
  
  				//	publish changes
  				ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					changes = [{type: REVERSE, path: ad.path}];
  					await callObservers(ad.observers, changes);
  				}
  				return observed.proxy;
  			},
  			sort: async function proxiedSort(target, observed, comparator) {
  				let i, l, item, ad, changes;
  				target.sort(comparator);
  				for (i = 0, l = target.length; i < l; i++) {
  					item = target[i];
  					if (item && typeof item === 'object') {
  						let tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							tmpObserved.ownKey = i;
  						}
  					}
  				}
  
  				//	publish changes
  				ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					changes = [{type: SHUFFLE, path: ad.path}];
  					await callObservers(ad.observers, changes);
  				}
  				return observed.proxy;
  			},
  			fill: async function proxiedFill(target, observed) {
  				let ad = getAncestorInfo(observed), normArgs, argLen,
  					start, end, changes = [], prev, tarLen = target.length, path;
  				normArgs = Array.from(arguments);
  				normArgs.splice(0, 2);
  				argLen = normArgs.length;
  				start = argLen < 2 ? 0 : (normArgs[1] < 0 ? tarLen + normArgs[1] : normArgs[1]);
  				end = argLen < 3 ? tarLen : (normArgs[2] < 0 ? tarLen + normArgs[2] : normArgs[2]);
  				prev = target.slice(0);
  				Reflect.apply(target.fill, target, normArgs);
  
  				for (let i = start, item, tmpTarget; i < end; i++) {
  					item = target[i];
  					// if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  					// 	target[i] = Array.isArray(item)
  					// 		? new ArrayObserver({target: item, ownKey: i, parent: observed}).proxy
  					// 		: new ObjectObserver({target: item, ownKey: i, parent: observed}).proxy;
  					// }
  					if (prev.hasOwnProperty(i)) {
  						tmpTarget = prev[i];
  						if (tmpTarget && typeof tmpTarget === 'object') {
  							let tmpObserved = tmpTarget[sysObsKey];
  							if (tmpObserved) {
  								tmpTarget = tmpObserved.revoke();
  							}
  						}
  
  						path = ad.path.slice(0);
  						path.push(i);
  						changes.push({type: UPDATE, path: path, value: target[i], oldValue: tmpTarget});
  					} else {
  						path = ad.path.slice(0);
  						path.push(i);
  						changes.push({type: INSERT, path: path, value: target[i]});
  					}
  				}
  
  				//	publish changes
  				if (ad.observers.length) {
  					await callObservers(ad.observers, changes);
  				}
  				return observed.proxy;
  			},
  			splice: async function proxiedSplice(target, observed) {
  				let ad = getAncestorInfo(observed),
  					spliceContent, spliceResult, changes = [], tmpObserved,
  					startIndex, removed, inserted, splLen, tarLen = target.length;
  
  				spliceContent = Array.from(arguments);
  				spliceContent.splice(0, 2);
  				splLen = spliceContent.length;
  
  				//	observify the newcomers
  				// for (let i = 2, item; i < splLen; i++) {
  				// 	item = spliceContent[i];
  				// 	if (item && typeof item === 'object' && !nonObservables.hasOwnProperty(item.constructor.name)) {
  				// 		spliceContent[i] = Array.isArray(item)
  				// 			? new ArrayObserver({target: item, ownKey: i, parent: observed}).proxy
  				// 			: new ObjectObserver({target: item, ownKey: i, parent: observed}).proxy;
  				// 	}
  				// }
  
  				//	calculate pointers
  				startIndex = splLen === 0 ? 0 : (spliceContent[0] < 0 ? tarLen + spliceContent[0] : spliceContent[0]);
  				removed = splLen < 2 ? tarLen - startIndex : spliceContent[1];
  				inserted = Math.max(splLen - 2, 0);
  				spliceResult = Reflect.apply(target.splice, target, spliceContent);
  				tarLen = target.length;
  
  				//	reindex the paths
  				for (let i = 0, item; i < tarLen; i++) {
  					item = target[i];
  					if (item && typeof item === 'object') {
  						tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							tmpObserved.ownKey = i;
  						}
  					}
  				}
  
  				//	revoke removed Observed
  				let i, l, item;
  				for (i = 0, l = spliceResult.length; i < l; i++) {
  					item = spliceResult[i];
  					if (item && typeof item === 'object') {
  						tmpObserved = item[sysObsKey];
  						if (tmpObserved) {
  							spliceResult[i] = tmpObserved.revoke();
  						}
  					}
  				}
  
  				//	publish changes
  				if (ad.observers.length) {
  					let index, path;
  					for (index = 0; index < removed; index++) {
  						path = ad.path.slice(0);
  						path.push(startIndex + index);
  						if (index < inserted) {
  							changes.push({
  								type: UPDATE,
  								path: path,
  								value: target[startIndex + index],
  								oldValue: spliceResult[index]
  							});
  						} else {
  							changes.push({type: DELETE, path: path, oldValue: spliceResult[index]});
  						}
  					}
  					for (; index < inserted; index++) {
  						path = ad.path.slice(0);
  						path.push(startIndex + index);
  						changes.push({type: INSERT, path: path, value: target[startIndex + index]});
  					}
  					await callObservers(ad.observers, changes);
  				}
  				return spliceResult;
  			},
  			set:async function set(target, observed, key, value) {
  				let oldValue = target[key], ad, changes;
  		
  				// if (value && typeof value === 'object' && !nonObservables.hasOwnProperty(value.constructor.name)) {
  				// 	target[key] = Array.isArray(value)
  				// 		? new ArrayObserver({target: value, ownKey: key, parent: this}).proxy
  				// 		: new ObjectObserver({target: value, ownKey: key, parent: this}).proxy;
  				// } else {
  				// 	target[key] = value;
  				// }
  				target[key] = value;
  		
  				if (oldValue && typeof oldValue === 'object') {
  					let tmpObserved = oldValue[sysObsKey];
  					if (tmpObserved) {
  						oldValue = tmpObserved.revoke();
  					}
  				}
  		
  				//	publish changes
  				ad = getAncestorInfo(observed);
  				if (ad.observers.length) {
  					ad.path.push(key);
  					changes = typeof oldValue === 'undefined'
  						? [{type: INSERT, path: ad.path, value: value}]
  						: [{type: UPDATE, path: ad.path, value: value, oldValue: oldValue}];
  					await callObservers(ad.observers, changes);
  				}
  				return true;
  			}
  		};
  		if (proxiedArrayMethods.hasOwnProperty(key)) {
  			return proxiedArrayMethods[key].bind(undefined, target, this);
  		} else {
  			return target[key];
  		}
  	}
  
  	async set(target, key, value) {
  		return false;
  		// let oldValue = target[key], ad, changes;
  
  		// // if (value && typeof value === 'object' && !nonObservables.hasOwnProperty(value.constructor.name)) {
  		// // 	target[key] = Array.isArray(value)
  		// // 		? new ArrayObserver({target: value, ownKey: key, parent: this}).proxy
  		// // 		: new ObjectObserver({target: value, ownKey: key, parent: this}).proxy;
  		// // } else {
  		// // 	target[key] = value;
  		// // }
  		// target[key] = value;
  
  		// if (oldValue && typeof oldValue === 'object') {
  		// 	let tmpObserved = oldValue[sysObsKey];
  		// 	if (tmpObserved) {
  		// 		oldValue = tmpObserved.revoke();
  		// 	}
  		// }
  
  		// //	publish changes
  		// ad = getAncestorInfo(this);
  		// if (ad.observers.length) {
  		// 	ad.path.push(key);
  		// 	changes = typeof oldValue === 'undefined'
  		// 		? [{type: INSERT, path: ad.path, value: value}]
  		// 		: [{type: UPDATE, path: ad.path, value: value, oldValue: oldValue}];
  		// 	await callObservers(ad.observers, changes);
  		// }
  		// return true;
  	}
  
  	async deleteProperty(target, key) {
  		let oldValue = target[key], ad, changes;
  
  		if (delete target[key]) {
  			if (oldValue && typeof oldValue === 'object') {
  				let tmpObserved = oldValue[sysObsKey];
  				if (tmpObserved) {
  					oldValue = tmpObserved.revoke();
  				}
  			}
  
  			//	publish changes
  			ad = getAncestorInfo(this);
  			if (ad.observers.length) {
  				ad.path.push(key);
  				changes = [{type: DELETE, path: ad.path, oldValue: oldValue}];
  				await callObservers(ad.observers, changes);
  			}
  			return true;
  		} else {
  			return false;
  		}
  	}
  }
  
  class ObjectObserver {
  	constructor(properties) {
  		let origin = properties.target, clone = {};
  		if (properties.parent === null) {
  			this.isRevoked = false;
  			this.observers = [];
  			Object.defineProperties(clone, observableDefinition);
  		} else {
  			this.parent = properties.parent;
  			this.ownKey = properties.ownKey;
  		}
  		prepareObject(origin, clone, this);
  		this.revokable = Proxy.revocable(clone, this);
  		this.proxy = this.revokable.proxy;
  		this.target = clone;
  	}
  
  	//	returns an unobserved graph (effectively this is an opposite of an ObjectObserver constructor logic)
  	revoke() {
  		//	revoke native proxy
  		this.revokable.revoke();
  
  		//	roll back observed graph to an unobserved one
  		let target = this.target, keys = Object.keys(target), l = keys.length, key, item;
  		while (l--) {
  			key = keys[l];
  			item = target[key];
  			if (item && typeof item === 'object') {
  				let tmpObserved = item[sysObsKey];
  				if (tmpObserved) {
  					target[key] = tmpObserved.revoke();
  				}
  			}
  		}
  		return target;
  	}
  
  	async set(target, key, value) {
  		let oldValue = target[key], ad, changes;
  
  		if (value && typeof value === 'object' && !nonObservables.hasOwnProperty(value.constructor.name)) {
  			target[key] = Array.isArray(value)
  				? new ArrayObserver({target: value, ownKey: key, parent: this}).proxy
  				: new ObjectObserver({target: value, ownKey: key, parent: this}).proxy;
  		} else {
  			target[key] = value;
  		}
  
  		if (oldValue && typeof oldValue === 'object') {
  			let tmpObserved = oldValue[sysObsKey];
  			if (tmpObserved) {
  				oldValue = tmpObserved.revoke();
  			}
  		}
  
  		//	publish changes
  		ad = getAncestorInfo(this);
  		if (ad.observers.length) {
  			ad.path.push(key);
  			changes = typeof oldValue === 'undefined'
  				? [{type: INSERT, path: ad.path, value: value}]
  				: [{type: UPDATE, path: ad.path, value: value, oldValue: oldValue}];
  			await callObservers(ad.observers, changes);
  		}
  		return true;
  	}
  
  	async deleteProperty(target, key) {
  		let oldValue = target[key], ad, changes;
  
  		if (delete target[key]) {
  			if (oldValue && typeof oldValue === 'object') {
  				let tmpObserved = oldValue[sysObsKey];
  				if (tmpObserved) {
  					oldValue = tmpObserved.revoke();
  				}
  			}
  
  			//	publish changes
  			ad = getAncestorInfo(this);
  			if (ad.observers.length) {
  				ad.path.push(key);
  				changes = [{type: DELETE, path: ad.path, oldValue: oldValue}];
  				await callObservers(ad.observers, changes);
  			}
  			return true;
  		} else {
  			return false;
  		}
  	}
  }
  
  class Observable {
  	constructor() {
  		throw new Error('Observable MAY NOT be created via constructor, see "Observable.from" API');
  	}
  
  	static from(target) {
  		if (target && typeof target === 'object' && !nonObservables.hasOwnProperty(target.constructor.name) && !('observe' in target) && !('unobserve' in target) && !('revoke' in target)) {
  			let observed = Array.isArray(target)
  				? new ArrayObserver({target: target, ownKey: null, parent: null})
  				: new ObjectObserver({target: target, ownKey: null, parent: null});
  			return observed.proxy;
  		} else {
  			if (!target || typeof target !== 'object') {
  				throw new Error('observable MAY ONLY be created from non-null object only');
  			} else if ('observe' in target || 'unobserve' in target || 'revoke' in target) {
  				throw new Error('target object MUST NOT have nor own neither inherited properties from the following list: "observe", "unobserve", "revoke"');
  			} else if (nonObservables.hasOwnProperty(target.constructor.name)) {
  				throw new Error(target + ' found to be one of non-observable object types: ' + nonObservables);
  			}
  		}
  	}
  
  	static isObservable(input) {
  		return typeof input === 'object' && input !== null &&
  			typeof input.revoke === 'function' &&
  			typeof input.observe === 'function' &&
  			typeof input.unobserve === 'function';
  	}
  }
  
  Object.freeze(Observable);
  module.exports = Observable;