UNPKG

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
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; } }