/**
* @copyright 2016 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* API Status: **Development**
* @module bajaux/registry/Registry
*/
define([ 'Promise',
'underscore',
'nmodule/js/rc/tinyevents/tinyevents',
'bajaux/registry/RegistryEntry',
'bajaux/registry/impl/JsRegistry' ], function (
Promise,
_,
tinyevents,
RegistryEntry,
JsRegistry) {
'use strict';
/**
* Query parameters to narrow the results resolved for a given registry key.
*
* @typedef {Object} module:bajaux/registry/Registry~QueryParams
* @property {String} [rjs] If given, only resolve entries that have this
* particular RequireJS ID.
* @property {Array.<Array.<String>>} [deps] If given, only resolve entries that have
* this exact set of RequireJS dependencies.
* @property {Array.<String>} [hasAny] If given, only resolve entries that
* have at least one of these `tags`.
* @property {Array.<String>} [hasAll] If given, only resolve entries that
* have all of these `tags`.
*/
/**
* Base class for Registry implementations (local JS registration,
* agent-based, etc).
*
* @class
* @alias module:bajaux/registry/Registry
* @param {Object} [obj] a JSON object to use to initially build this registry
* (typically will be used to reconstitute a registry using the previous
* output of `toJSON`). If omitted, registry will be empty on creation.
* @since Niagara 4.10
*/
class Registry {
constructor(obj) {
this.$memoize();
tinyevents(this);
const reg = this.$localReg = new JsRegistry((obj || {}).localReg);
reg.valueToKey = (value) => this.valueToKey(value);
}
/**
* @returns {module:bajaux/registry/Registry}
*/
getLocal() {
return this.$localReg;
}
/**
* Register a RequireJS module ID locally for the given key.
*
* @param {String} key
* @param {module:bajaux/registry/Registry~QueryParams} [params]
* query parameters/metadata for this RequireJS module
* @returns {Promise} promise to be resolved after registration is complete
*/
register(key, params) {
return Promise.try(() => {
this.$localReg.register(key, params);
//local registrations can happen after querying the registry, so we must
//clear memoization caches to ensure the registration takes effect.
this.$clearMemoization();
this.emit('changed');
});
}
valueToKey(value) {
return String(value);
}
/**
* By default, just queries from the entries registered locally. Most likely,
* subclasses will override this with something more useful.
*
* @param {*} value
* @param {module:bajaux/registry/Registry~QueryParams} params
* @returns {Promise.<Array.<module:bajaux/registry/RegistryEntry>>}
* promise to be resolved with an array of all matching `RegistryEntry`s
*/
queryAll(value, params) {
return Promise.resolve(this.$localReg.queryAll(value, params));
}
/**
* By default, just queries from the entries registered locally. Most likely,
* subclasses will override this with something more useful.
* @param {*} value
* @param {module:bajaux/registry/Registry~QueryParams} params
* @returns {Promise.<module:bajaux/registry/RegistryEntry>}
* promise to be resolved with the first matching`RegistryEntry`
*/
queryFirst(value, params) {
return Promise.resolve(this.$localReg.queryFirst(value, params));
}
/**
* Perform a query on the registry and resolve all RequireJS modules
* represented. This will resolve an array of Widget constructors, menu
* agent functions, etc.
*
* @param {*} value
* @param {module:bajaux/registry/Registry~QueryParams} params
* @returns {Promise.<Array.<*>>} promise to be resolved with an array of the
* exported results of all RequireJS modules represented, or empty if none
* found; rejected if the station could not be successfully queried for
* registry info, or if any of the RequireJS modules failed to resolve
*/
resolveAll(value, params) {
return this.queryAll(value, params).then(resolveThemAll);
}
/**
* Perform a query on the registry and attempt to resolve the first
* matching entry's RequireJS module. Note that this differs from `resolveAll`
* in that if the first entry fails to resolve (for instance, an invalid
* RequireJS module ID), it will move on to the next entry and keep trying
* to resolve all the way down until it can resolve *something*.
*
* @param {*} value
* @param {module:bajaux/registry/Registry~QueryParams} params
* @returns {Promise.<*>} promise to be resolved with the exported
* results of the first matching entry that successfully resolves its
* RequireJS module ID, or undefined if none found
*/
resolveFirst(value, params) {
return this.queryFirst(value, params)
.then((entry) => entry &&
entry.resolve().catch(() => this.queryAll(value, params).then(resolveFirst)));
}
/**
* Return an object suitable for serialization using `JSON.stringify` or
* similar. The returned object can be passed right back to a
* `Registry` constructor to reconstitute later.
*
* @returns {Object}
*/
toJSON() {
return { localReg: this.$localReg.toJSON() };
}
/**
* Set up memoizing on all registry query calls. Since registry contents
* do not change at runtime, this should be safe to call.
* @private
*/
$memoize() {
this.queryAll = _.memoize(this.queryAll, hashKeyAndParams);
this.queryFirst = _.memoize(this.queryFirst, hashKeyAndParams);
this.resolveAll = _.memoize(this.resolveAll, hashKeyAndParams);
this.resolveFirst = _.memoize(this.resolveFirst, hashKeyAndParams);
}
/**
* Clear memoization caches.
* @private
*/
$clearMemoization() {
if (!this.queryAll.cache) {
return; //memoization not done
}
this.queryAll.cache = {};
this.queryFirst.cache = {};
this.resolveAll.cache = {};
this.resolveFirst.cache = {};
}
}
////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////
function hashKeyAndParams(key, params) {
return key + ',' + RegistryEntry.$hashParams(params);
}
function resolveIt(entry) { return entry.resolve(); }
function resolveThemAll(entries) { return Promise.all(_.map(entries, resolveIt)); }
/**
* Resolve the first entry in the list. If it fails to resolve, walk down
* the list until we find one that does. If all fail to resolve, just resolve
* falsy.
*
* @inner
* @param {Array.<module:bajaux/registry/RegistryEntry>} entries
* @returns {Promise}
*/
function resolveFirst(entries) {
return Promise.resolve(entries.length && entries[0].resolve())
.catch(() => resolveFirst(entries.slice(1)));
}
return Registry;
});