State.js

"use strict";

// Binding will be late bound, due to circular dependency between it and State.
let Binding;

/**
 * Stores the state of an API request.
 */
class State {

	/**
	 * @constructor
	 * @param {object|function} value The object of the {@link Api} that created this {@link State}.
	 * @param {any[]} route The route of the {@link Api} that created this {@link State}.
	 * @param {string} type "route" if this State is given to a router and "close" if given to a closer.
	 * @param {object} origin The origin of the {@link Api} that created this {@link State}.
	 * @param {Binding} binding The {@link Binding} object of {@link State#value}. This has to be set, because {@link Binding~types.clone clone}-Bindings do not bind the object itself and thus hold no reference to the {@link Binding}.
	 */
	constructor(value, route, type, origin, binding) {
		if(!Array.isArray(route))
			throw new TypeError("State route has to be an array.");
		if(!Binding || !(binding instanceof Binding))
			throw new TypeError("State binding has to be an instance of Binding.");

		Object.defineProperties(this, {

			/**
			 * The `value` that was given to the constructor.
			 * @name State#value
			 * @type {object|function}
			 */
			value: {
				enumerable: true,
				value
			},

			/**
			 * The `route` that was given to the constructor.
			 * @name State#route
			 * @type {any[]}
			 */
			route: {
				enumerable: true,
				value: route.slice(0)
			},

			/**
			 * The `type` ("route" or "close") that was given to the constructor.
			 * @name State#type
			 * @type {string}
			 */
			type: {
				enumerable: true,
				value: type
			},

			/**
			 * The `origin` that was given to the constructor.
			 * @name State#origin
			 * @type {object}
			 */
			origin: {
				enumerable: true,
				value: origin
			},

			/**
			 * The `binding` that was given to the constructor.
			 * @name State#binding
			 * @type {object}
			 */
			binding: {
				enumerable: true,
				value: binding
			},

			/**
			 * `true` if this state was the result of a {@link State#setValue} call, `false` elsewise.
			 * @name State#modified
			 * @type {boolean}
			 */
			modified: {
				value: false
			}
		});

		Object.freeze(this);
	}

	/**
	 * Returns a new state, that prototypically inherits from this State, but with another {@link Binding#value value}.
	 * The new state will get an active {@link State#modified} flag.
	 * @param {any} valueDescriptor The new value.
	 * @return {State} A new State.
	 */
	setValue(valueDescriptor) {
		if(typeof valueDescriptor !== "object" || valueDescriptor == null)
			throw new TypeError("State valueDescriptor has to be an object.");
		valueDescriptor.enumerable = true;

		return Object.freeze(Object.create(this, {
			value: valueDescriptor,
			modified: {
				value: true
			}
		}));
	}

	static setBinding(val) {
		Binding = val;
	}
}

module.exports = State;