'use strict';
/**
* A framework for all $wider bundles but also a tracker for different modules of the same name being loaded by different packages of the same
*
* creates
* ```javascript
* global.$wider = { // container for other wider globals
* moduleLoaded(moduleName){} // which throws error on duplicate loads
* }
* ```
* @module @wider/registry
*/
const $moduleName = "@wider/registry";
const $objectName = $moduleName.split(/\//)[1];
const widerGlobal = {};
const modulesLoaded = {};
const root = "<<root>>";
/**
* Extends `Error@ -with its additional properties and also logs the error to the console
* @property {String} message - the error message
* @property {NCName} code a code word for optional programmatic use by code that traps the error
* @property {id|Object} moduleName the name of the module affected if present. If provided as an object, then the object should have a `.moduleName` property
*/
class loggedError extends Error {
constructor(message, code, defaultExport) {
super(`${$moduleName || 'anonymousModule'} - ${message}`);
this.code = (code || "UNSPECIFIED");
this.moduleName = defaultExport.$moduleName || defaultExport;
console.error(this);
}
}
loggedError.$moduleName = `${$moduleName}[throw loggedError]`;
/**
* Registers an object typically as it is being constructed via require or import. The purpose being to ensure that multiple packages within your runtime
* on a given cluster do not load the same module more than once - as can happen when a package is a dependency of several other packages. This function should *only*
* only be called from within the module level code of the module that is registering itself
*
* @param {String|Dictionary} [moduleName] - the name of the item to be registered. The string should be the same as used as might be used as id in `require`
* or `import`, or as used in `npm install`
*
* If moduleName is an object it is treated as a dictionary of registrations where the keyName is si the moduleName and the associated value
* is an object with the properties ***value*** and ***objectName***. It may also contain ***section^^^ which if present will override the section parameter of the call to `.register()`
*
* If *moduleName* is absent then nothing is registered and the only action is to return the output Dictionary bundle
*
* @param {Object} [section] if absent then the value is saved under ***objectName*** at the top level of the output Dictionary, otherwise this defines the section
* name in the dictionary under which the value is filed
* @param {*} [value] - if present then the ***moduleName*** must be supplied and this ***value*** is added to the bundle.
*
* If absent, then the ***moduleName***, if provided, is registered in the dictionary and nothing is added to the bundle.
*
* If this is an object (class, function, json, plain object) then it should have a `$moduleName` static property which is the same as the parameter `moduleName`.
* @param {NCName} [objectName] if objectName is present, then ***value*** must be supplied and he value is stored in the response of this `@wider/registry
* response under this name available for direct use by any other module without use of `require` or `import`. If ***objectName*** is not provided then it will
* be assumed to be the same as ***moduleName***
*
* @returns {Dictionary} containing all registered items under their objectname and section.
* @throws {loggedError} an extension of the javascript Error object thrown when a registration is rejected
*/
function register(moduleName, section, value, objectName) {
if (moduleName) {
if (typeof moduleName === "string") {
if (!/^[\w_\-\d\@\/\.]+$/.test(moduleName))
throw new loggedError(`module name '${moduleName}' is not a valid simple module id`, "INVALID_MODULE_NAME", $moduleName);
if (modulesLoaded[moduleName])
throw new loggedError(`${moduleName} - You have multiple packages attempting to load versions of the same moduleName`, "DUPLICATE_CALL", $moduleName);
else if (objectName && !/^\w[\w\d_]*$/)
throw new loggedError(`${moduleName} - Invalid format for name of objectName ${objectName}`, "BAD_OBJECTNAME", $moduleName);
else if (section && !/^\w[\w\d_]*$/)
throw new loggedError(`${moduleName} - Invalid format for name of objectName ${section}`, "BAD_SECTION", $moduleName);
else {
modulesLoaded[moduleName] = {
created: Date.now(),
};
if (value !== undefined) {
const target = section ? widerGlobal[section] || (widerGlobal[section] = {}) : widerGlobal;
if (!objectName)
throw new loggedError(`${moduleName} - objectName must be provided when value is provided`, "MISSING_OBJECTNAME", $moduleName);
else if (target[objectName])
throw new loggedError(`Duplicate attempt to save member ${objectName} into $wider`, "DUPLICATE_GLOBAL_MEMBER", $moduleName);
else if (value.$moduleName != moduleName && value.$objectName != moduleName)
throw new loggedError(`The submitted object does not possess a $moduleName which is the same as the name of the registry name or a matching alias defined by $objectName `, "$MODULENAME_MISSING", $moduleName);
else {
target[moduleName] = value;
Object.assign(modulesLoaded[moduleName], {
objectName: objectName,
section: section,
path: (section ? section + "." : "") + objectName,
value: value
});
}
}
}
} else {
const entries = moduleName;
if (objectName || value)
throw new loggedError("Objectname and value not permitted when submitting a batch of registrations", "INVALID_PARAMS", register.$moduleName);
else if (section) {
if (typeof section != "string")
throw new loggedError("Section name missing or not a string", "SECTION_MISSING", register.$moduleName);
if (!modulesLoaded[section])
register(section, null, {
$moduleName: section
});
}
for (const key in entries) {
if (Object.hasOwnProperty.call(entries, key)) {
const element = entries[key];
if (!element.$moduleName)
throw new loggedError(`Entry submitted for '${section||root}[${key}]' does not have property $moduleName`, "MODULENAME_MISSING", register.$moduleName);
register(key, element.section || section, element, element.objectName || element.$moduleName);
}
}
}
}
// makes a normally hidden non-enumerable member of global - we must always be the first to set global.$wider
if (!global.$wider) {
Object.defineProperties(global, {
"$wider": {
get: () => {
return widerGlobal;
}
},
});
}
return widerGlobal;
}
register.$moduleName = `${$moduleName[register]}`;
/**
* Generate a report of the items that have been loaded and how long into the run session it was
* when they are loaded. NB not all loaded items are published.
* @returns {String} a tabular report of items loaded
*/
function loaded() {
const result = ["Modules loaded in chronological order of loading\n\n"];
const paddingName = 40;
const paddingWait = 10;
const started = (modulesLoaded.CONFIG || modulesLoaded[$objectName]).created;
for (const key in modulesLoaded) {
if (Object.hasOwnProperty.call(modulesLoaded, key)) {
const element = modulesLoaded[key];
result.push(key.padEnd(paddingName, " ") + " " +
((element.created - started) / 1000).toFixed(3).padStart(paddingWait, " ") + " " +
(element.path || ""));
}
}
return result.join("\n");
}
/**
* A report of the structure in the object that is published via the common object
* @returns {String} a tabular report of items published
*/
function published() {
const result = ["Modules published in alphabetical order\n\n"];
const paddingName = 40;
//const paddingWait = 10;
for (const key in widerGlobal) {
if (Object.hasOwnProperty.call(modulesLoaded, key)) {
const element = modulesLoaded[key];
result.push(key.padEnd(paddingName, " ") + " " +
(element.path || ""));
if (element.value.$moduleName)
for (const key2 in element.value) {
if (Object.hasOwnProperty.call(element.value, key2)) {
const element2 = element.value[key2];
result.push((key + "." + key2).padEnd(paddingName, " ") + " " +
(typeof element2) +
((key2==="$moduleName" || key2==="$objectName") ? ` : ${element2}`:""));
}
}
}
}
return result.sort().join("\n");
}
// register myself
register({
registry: {
$moduleName: $moduleName,
$objectName: $objectName,
loaded: loaded,
published: published,
register: register,
loggedError: loggedError
}
});
// there is a risk that this module itself is being multiple loaded so we have to use the version in global even if we didn't create it
//register($moduleName); // to stop ourselves being loaded multiple times
module.exports = global.$wider;