const Utils = require('./utils');
/**
* Central registry for processes.
*
* @class
*/
class ProcessRegistry {
/**
* Creates a new registry of all processes.
*
* @param {Array.<object>|ProcessRegistry} [processes=[]] - Optionally, a list of predefined processes.
* @param {boolean} [addNamespace=false] - Add a namespace property to processes if set to `true`.
*/
constructor(processes = [], addNamespace = false) {
/**
* List of listeners for change events.
* @public
*/
this.listeners = [];
/**
* Object of namespaces and processes.
* @protected
* @type {object.<string,object.<string,object>>}
*/
this.processes = {};
/**
* Add a namespace property to processes if set to `true`.
* @protected
* @type {boolean}
*/
this.addNamespace = addNamespace;
// Fill process list
if (processes instanceof ProcessRegistry) {
for(let namespace in processes.processes) {
this.addAll(processes.processes[namespace]);
}
}
else {
this.addAll(processes);
}
}
/**
* Event that is fired on changes, notifies listeners.
*
* @param {string} event - One of 'add', 'addAll' or 'remove'.
* @param {*} data
* @param {string} namespace
*/
onChange(event, data, namespace) {
for(let listener of this.listeners) {
listener(event, data, namespace);
}
}
/**
* Adds a list of processes for a given namespace.
*
* Replaces an existing process in the given namespace if it exists.
*
* Fires 'addAll' event.
*
* @param {Array.<object>} processes Optionally, a list of processes
* @param {string} [namespace="backend"] The namespace for the processes (defaults to 'backend', i.e. pre-defined processes)
*/
addAll(processes, namespace = 'backend') {
for(var i in processes) {
this.add(processes[i], namespace, false);
}
this.onChange('addAll', processes, namespace);
}
/**
* Adds a single process to a given namespace.
*
* Replaces an existing process in the given namespace if it exists.
*
* Fires 'add' event.
*
* @param {object} processes A process definition
* @param {string} [namespace="backend"] The namespace for the process (defaults to 'backend', i.e. pre-defined processes)
*/
add(process, namespace = 'backend', fireEvent = true) {
if (!Utils.isObject(process)) {
throw new Error("Invalid process; not an object.");
}
if (typeof process.id !== 'string') {
throw new Error("Invalid process; no id specified.");
}
if (typeof namespace !== 'string') {
throw new Error("Invalid namespace; not a string.");
}
if (!this.processes[namespace]) {
this.processes[namespace] = {};
}
process = Object.assign(this.addNamespace ? {namespace} : {}, process);
this.processes[namespace][process.id] = process;
if (fireEvent) {
this.onChange('add', process, namespace);
}
}
/**
* Returns the count of all processes independant of the namespaces.
*
* @returns {number}
*/
count() {
return Utils.size(this.all());
}
/**
* Returns all processes as a list, independant of the namespaces.
*
* @returns {Array.<object>}
*/
all() {
let processes = [];
for(let ns in this.processes) {
processes = processes.concat(Object.values(this.processes[ns]));
}
return processes;
}
/**
* Checks whether a namespace exists (i.e. at least one process for the namespace exists)
*
* @param {string} namespace The namespace
* @returns {boolean}
*/
hasNamespace(namespace) {
if(typeof namespace !== 'string') {
return false;
}
return Boolean(this.processes[namespace]);
}
/**
* Returns a (sorted) list of all available namespaces.
*
* @returns {Array.<string>}
*/
namespaces() {
return Object.keys(this.processes).sort();
}
/**
* Returns all processes from a specific namespace.
*
* Returns an empty list if the namespace is not defined.
*
* @param {string} namespace The namespace of the processes to return (e.g. 'backend' for pre-defined processes)
* @returns {Array.<object>}
*/
namespace(namespace) {
if(typeof namespace !== 'string') {
return [];
}
let processes = this.processes[namespace];
return processes ? Object.values(processes) : [];
}
/**
* Checks whether a process with the given ID exists in the given namespace.
*
* If the namespace is set to `null` (default) then it checks both user processes and backend processes.
* The default namespace for pre-defined processes is `backend`.
*
* @param {string} id The process identifier
* @param {?string} [namespace=null] The namespace of the process
* @returns {boolean}
*/
has(id, namespace = null) {
return Boolean(this.get(id, namespace));
}
/**
* Retrieve the process with the given ID fron the given namespace.
*
* If the namespace is set to `null` (default) then it retrieces from both (1) `user` processes and (2) `backend` processes
* with preference to user processes on conflict. The default namespace for pre-defined processes is `backend`.
*
* @param {string} id The process identifier
* @param {?string} [namespace=null] The namespace of the process
* @returns {object}
*/
get(id, namespace = null) {
if (typeof id !== 'string') {
return null;
}
// If no namespace is set, prefer the user namespace over backend namespace
if (namespace === null) {
return this.get(id, 'user') || this.get(id, 'backend');
}
if (this.processes[namespace]) {
return this.processes[namespace][id] || null;
}
return null;
}
/**
* Removes a single process or a complete namespace from the registry.
*
* If nothing is given, removes the namespace 'user'.
* If only a namespace is given, removes the whole namespace.
* If only a process is given, removes a process from the namespace `user`.
* If both parameters are given, removes a process from the given namespace.
*
* Returns `true` on succes, `false` on failure.
*
* Fires 'remove' event.
*
* @param {?string} [id=null] The process identifier
* @param {?string} [namespace="user"] The namespace, defaults to `user`
* @returns {boolean}
*/
remove(id = null, namespace = 'user') {
if (typeof namespace !== 'string') {
return false;
}
if (this.processes[namespace]) {
if (typeof id === 'string') {
if (this.processes[namespace][id]) {
let process = this.processes[namespace][id];
delete this.processes[namespace][id];
if (Utils.size(this.processes[namespace]) === 0) {
delete this.processes[namespace];
}
this.onChange('remove', process, namespace);
return true;
}
}
else {
delete this.processes[namespace];
this.onChange('remove', null, namespace);
return true;
}
}
return false;
}
}
module.exports = ProcessRegistry;