wj-config
Version:
Javascript configuration module for NodeJS and browser frameworks such as React that works like ASP.net configuration where data sources are specified (usually JSON files) and environment variables can contribute/overwrite values by following a naming con
113 lines (112 loc) • 4.36 kB
JavaScript
import { isConfigNode } from "../helpers.js";
import makeWsUrlFunctions from "../makeWsUrlFunctions.js";
import merge from "../merge.js";
export class BuilderImpl {
/**
* Collection of data sources added to the builder.
*/
#dsDefs = [];
/**
* URL data used to create URL functions out of specific property values in the resulting configuration object.
*/
#urlData;
/**
* Flag to determine if the last call in the builder was the addition of a data source.
*/
_lastCallWasDsAdd = false;
#postMergeFns = [];
postMerge(fn) {
this._lastCallWasDsAdd = false;
this.#postMergeFns.push(fn);
}
add(dataSource) {
this.#dsDefs.push({
dataSource: dataSource
});
dataSource.index = this.#dsDefs.length - 1;
this._lastCallWasDsAdd = true;
}
name(name) {
if (!this._lastCallWasDsAdd) {
throw new Error('Names for data sources must be set immediately after adding the data source or setting its conditional.');
}
this.#dsDefs[this.#dsDefs.length - 1].dataSource.name = name;
}
when(predicate, dataSourceName) {
if (!this._lastCallWasDsAdd) {
throw new Error('Conditionals for data sources must be set immediately after adding the data source or setting its name.');
}
if (this.#dsDefs[this.#dsDefs.length - 1].predicate) {
throw new Error('Cannot set more than one predicate (conditional) per data source, and the last-added data source already has a predicate.');
}
const dsDef = this.#dsDefs[this.#dsDefs.length - 1];
dsDef.predicate = predicate;
if (dataSourceName != undefined) {
this.name(dataSourceName);
}
}
createUrlFunctions(wsPropertyNames, routeValuesRegExp) {
this._lastCallWasDsAdd = false;
let propNames;
if (typeof wsPropertyNames === 'string') {
if (wsPropertyNames !== '') {
propNames = [wsPropertyNames];
}
}
else if (Array.isArray(wsPropertyNames) && wsPropertyNames.length > 0) {
propNames = wsPropertyNames;
}
else {
throw new Error("The 'wsPropertyNames' property now has no default value and must be provided.");
}
this.#urlData = {
wsPropertyNames: propNames,
routeValuesRegExp: routeValuesRegExp ?? /\{(\w+)\}/g
};
}
async build(traceValueSources, evaluatePredicate) {
this._lastCallWasDsAdd = false;
const qualifyingDs = [];
let wjConfig = {};
if (this.#dsDefs.length > 0) {
// Prepare a list of qualifying data sources. A DS qualifies if it has no predicate or
// the predicate returns true.
this.#dsDefs.forEach(ds => {
if (!ds.predicate || evaluatePredicate(ds.predicate)) {
qualifyingDs.push(ds.dataSource);
}
});
if (qualifyingDs.length > 0) {
const dsTasks = [];
qualifyingDs.forEach(ds => {
dsTasks.push(ds.getObject());
});
const sources = await Promise.all(dsTasks);
wjConfig = merge(sources, traceValueSources ? qualifyingDs : undefined);
}
}
if (this.#urlData) {
this.#urlData.wsPropertyNames.forEach((value) => {
const obj = wjConfig[value];
if (isConfigNode(obj)) {
makeWsUrlFunctions(obj, this.#urlData.routeValuesRegExp, globalThis.window && globalThis.window.location !== undefined);
}
else {
throw new Error(`The level 1 property "${value}" is not a node value (object), but it was specified as being an object containing URL-building information.`);
}
});
}
if (traceValueSources) {
if (qualifyingDs.length > 0) {
wjConfig._qualifiedDs = qualifyingDs.map(ds => ds.trace());
}
else {
wjConfig._qualifiedDs = [];
}
}
for (let fn of this.#postMergeFns) {
wjConfig = await fn(wjConfig);
}
return wjConfig;
}
}