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

3 lines (2 loc) 14.5 kB
function b(r){return Array.isArray(r)}function f(r){return typeof r=="object"&&r!==null&&!b(r)&&!(r instanceof Date)&&!(r instanceof Set)&&!(r instanceof Map)&&!(r instanceof WeakMap)&&!(r instanceof WeakRef)&&!(r instanceof WeakSet)}function A(r){return typeof r=="function"}const p=(r,t)=>{if(!A(t))throw new Error("The provided loop body is not a function.");if(r)for(const[e,n]of Object.entries(r)){let i=0;if(t(e,n,i++))break}},F=r=>{const t=/^-?\d+$/,e=/^0x[0-9a-fA-F]+$/,n=/^-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?$/;if(r==null)return r;if(typeof r!="string")throw new Error("The value to parse must always be of the string data type.");if(r==="true"||r==="false")return r==="true";let i=null;return t.test(r)?i=Number.parseInt(r,10):e.test(r)?i=Number.parseInt(r,16):n.test(r)&&(i=Number.parseFloat(r)),i!==null&&!isNaN(i)?i:r.toString()};class m{name;index;_traceObject;constructor(t){this.name=t}trace(){return this._traceObject=this._traceObject??{name:this.name,index:this.index}}}const P=(r,t,e)=>(e&&(r=r.substring(e.length)),r.split(t)),q=(r,t)=>(r[t]===void 0&&(r[t]={}),r[t]);class w extends m{#t;#n;#e;#r(){let t=n=>!0,e="";return this.#e&&(typeof this.#e=="string"?(e=this.#e,t=n=>n.toString().startsWith(e)):t=this.#e),[t,e]}#i(t){if(!f(t))throw new Error("The provided dictionary must be a flat object.");const[e,n]=this.#r();p(t,(i,s)=>{if(!e(i))return!1;if(f(s))throw new Error(`The provided dictionary must be a flat object: Property ${i} has a non-scalar value.`)})}#s(t){const e={};if(!t)return e;const[n,i]=this.#r();return p(t,(s,u)=>{if(n(s)){const h=P(s,this.#n,i);let o=e,a="";for(let c=0;c<h.length-1;++c)if(a+=(a.length?".":"")+h[c],o=q(o,h[c]),!f(o))throw new Error(`Cannot set the value of property "${s}" because "${a}" has already been created as a leaf value.`);if(o[h[h.length-1]])throw new Error(`Cannot set the value of variable "${s}" because "${h[h.length-1]}" has already been created as an object to hold other values.`);typeof u=="string"&&(u=F(u)),o[h[h.length-1]]=u}}),e}constructor(t,e,n){if(super("Dictionary"),!e)throw new Error("Dictionaries must specify a hierarchy separator.");if(typeof e!="string")throw new Error("The hierarchy separator must be a string.");if(this.#n=e,n!==void 0){if(typeof n=="string"&&n.length===0)throw new Error("The provided prefix value cannot be an empty string.");if(typeof n!="string"&&typeof n!="function")throw new Error("The prefix argument can only be a string or a function.");if(typeof n=="string"&&n.length===0)throw new Error("An empty string cannot be used as prefix.")}this.#e=n,typeof t!="function"&&this.#i(t),this.#t=t}async getObject(){let t=this.#t;t&&typeof t=="function"&&(t=await t(),this.#i(t));const e=this.#s(t);return Promise.resolve(e)}}class y extends w{constructor(t,e){if(super(t,"__",e),!e)throw new Error("The prefix is mandatory to avoid accidental imports of sensitive data from environment variable values.");this.name="Environment"}}class E extends m{_input;_required;_init;_processFn;constructor(t,e=!0,n,i){super(typeof t=="string"?`Fetch ${t}`:"Fetched Configuration"),this._input=t,this._required=e,this._init=n,this._processFn=i??(async s=>{if(this._required&&s.status===204)throw new Error(`${this.name}: Configuration data from this source is required but the fetch operation yielded no data.`);if(this._required&&s.status===404)throw new Error(`${this.name}: Configuration data from this source is required but the fetch operation could not find the resource.`);if(this._required&&!s.ok)throw new Error(`${this.name}: Configuration data from this source is required but the fetch operation yielded a non-OK response: ${s.status} (${s.statusText}).`);return s.ok&&s.status!==204?await s.json():null})}async getObject(){let t=this._input;typeof t=="function"&&(t=await t());let e={};try{const n=await fetch(t,this._init);try{e=await this._processFn(n)}catch(i){if(console.debug("Error processing fetched response: %o",i),this._required)throw new Error(`${this.name}: An error occurred while processing the fetched response.`,{cause:i})}}catch(n){if(this._required)throw n}return e}}class _ extends m{_json;_jsonParser;_reviver;constructor(t,e,n){super("JSON Data"),this._json=t,this._jsonParser=e??JSON,this._reviver=n}async getObject(){let t=this._json;return typeof t=="function"&&(t=await t()),this._jsonParser.parse(t,this._reviver)}}class T extends m{_obj;#t(t){if(!f(t))throw new Error("The provided object is not suitable as configuration data source.")}constructor(t){super("Object"),typeof t!="function"&&this.#t(t),this._obj=t}async getObject(){let t=this._obj;return typeof t=="function"&&(t=await t()),this.#t(t),Promise.resolve(t)}}function x(r,t){if(!r)throw new Error("No valid path was provided.");const e=(n,i)=>{const s={};return s[n]=i,s};return typeof r=="function"?async()=>{const[n,i]=await r();return e(n,i)}:e(r,t)}class D extends w{constructor(t,e,n=":"){super(x(t,e),n),typeof t=="string"?this.name=`Single Value: ${t}`:this.name="Single Value"}}const v=r=>"",g="_rootPath",U=["host","rootPath"],O=["host","rootPath","scheme","port"];function W(r,t,e,n){let i,s=0;t&&(typeof t=="function"?i=t:b(t)?i=o=>t[s++]:i=o=>t[o]);let h=`${(this[g]??v)()}${r??""}`;if(e&&i&&e.test(h)&&(h=h.replace(e,(o,a)=>encodeURIComponent((i??v)(a)))),n){const o=h.indexOf("?");o<0?h+="?":h.length-o-1&&(h+="&");let a;if(typeof n=="function"?a=n():a=n,typeof a=="string")h+=a;else{let c="";p(a,(d,l)=>{c+=`${encodeURIComponent(d)}=${encodeURIComponent(l)}&`}),c.length>0&&(h+=c.substring(0,c.length-1))}}return h}function I(r){return!this.host&&!r||!this.host&&!this.port&&!this.scheme?this.rootPath??"":`${this.scheme??"http"}://${this.host??globalThis.window?.location?.hostname??""}${this.port?`:${this.port}`:""}${this.rootPath??""}`}function R(r){return`${(r[g]??v)()}${this.rootPath??""}`}function C(r,t,e,n){if(!r)return;if(!f(r))throw new Error(`Cannot operate on a non-object value (provided value is of type ${typeof r}).`);const i=o=>{const a=["host","port","scheme","rootPath","buildUrl"];return!o.toString().startsWith("_")&&!a.includes(o.toString())},s=o=>{let a=!1;return p(o,c=>a=U.includes(c)||e&&O.includes(c)),a},u=(o,a)=>!!a?.[g]||!!o._rootPath;s(r)&&!n?.buildUrl?r[g]=function(){return I.bind(r)(e)}:u(r,n)&&(r[g]=function(){return R.bind(r)(n)}),u(r,n)&&(r.buildUrl=function(o,a,c){return W.bind(r)(o,a,t,c)});const h=u(r,void 0);p(r,(o,a)=>{const c=i(o);c&&h&&typeof a=="string"?r[o]=function(d,l){return(r.buildUrl??v)(a,d,l)}:c&&f(a)&&C(a,t,e,h?r:void 0)})}function $(r,t,e){let n;return p(t,(i,s)=>{const u=r[i];if(u!==void 0){if(f(u)&&!f(s))throw new Error(`The destination value of property "${i}" is an object, but the second object is not providing an object value.`);if(!f(u)&&f(s))throw new Error(`The destination value of property "${i}" is a scalar/array value, but the second object is not providing a scalar/array value.`);f(u)?(e&&(n={trace:e.trace[i]=e.trace[i]??{},dataSource:e.dataSource}),$(u,s,n)):(r[i]=s,e&&(e.trace[i]=e.dataSource.trace()))}else e&&f(s)?(r[i]={},n={trace:e.trace[i]=e.trace[i]??{},dataSource:e.dataSource},$(r[i],s,n)):(r[i]=s,!f(s)&&e&&(e.trace[i]=e.dataSource.trace()))}),r}function M(r,t){if(!b(r))throw new Error("The provided value is not an array of objects.");if(r.length===0||!f(r[0])||r[0]===null||r[0]===void 0)throw new Error("The first element of the array is required and must be a suitable configuration object.");if(t&&r.length!==t?.length)throw new Error("The number of provided objects differs from the number of provided data sources.");let e={},n;t&&(n={trace:{},dataSource:t[0]});for(let i=0;i<r.length;++i){let s=r[i];if(s==null)s={};else if(!f(s))throw new Error(`Configuration object at index ${i} is not of the appropriate type.`);n&&(n.dataSource=t[i]),$(e,s,n)}return n&&(e._trace=n.trace),e}class J{#t=[];#n;_lastCallWasDsAdd=!1;#e=[];postMerge(t){this._lastCallWasDsAdd=!1,this.#e.push(t)}add(t){this.#t.push({dataSource:t}),t.index=this.#t.length-1,this._lastCallWasDsAdd=!0}name(t){if(!this._lastCallWasDsAdd)throw new Error("Names for data sources must be set immediately after adding the data source or setting its conditional.");this.#t[this.#t.length-1].dataSource.name=t}when(t,e){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.#t[this.#t.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 n=this.#t[this.#t.length-1];n.predicate=t,e!=null&&this.name(e)}createUrlFunctions(t,e){this._lastCallWasDsAdd=!1;let n;if(typeof t=="string")t!==""&&(n=[t]);else if(Array.isArray(t)&&t.length>0)n=t;else throw new Error("The 'wsPropertyNames' property now has no default value and must be provided.");this.#n={wsPropertyNames:n,routeValuesRegExp:e??/\{(\w+)\}/g}}async build(t,e){this._lastCallWasDsAdd=!1;const n=[];let i={};if(this.#t.length>0&&(this.#t.forEach(s=>{(!s.predicate||e(s.predicate))&&n.push(s.dataSource)}),n.length>0)){const s=[];n.forEach(h=>{s.push(h.getObject())});const u=await Promise.all(s);i=M(u,t?n:void 0)}this.#n&&this.#n.wsPropertyNames.forEach(s=>{const u=i[s];if(f(u))C(u,this.#n.routeValuesRegExp,globalThis.window&&globalThis.window.location!==void 0);else throw new Error(`The level 1 property "${s}" is not a node value (object), but it was specified as being an object containing URL-building information.`)}),t&&(n.length>0?i._qualifiedDs=n.map(s=>s.trace()):i._qualifiedDs=[]);for(let s of this.#e)i=await s(i);return i}}class V{_envSource;#t;constructor(t,e){this._envSource=t,this.#t=e}add(t){return this.#t.add(t),this}addObject(t){return this.add(new T(t))}addDictionary(t,e=":",n){return this.add(new w(t,e,n))}addEnvironment(t,e="OPT_"){return this.add(new y(t,e))}addFetched(t,e=!0,n,i){return this.add(new E(t,e,n,i))}addJson(t,e,n){return this.add(new _(t,e,n))}addSingleValue(t,e,n){return this.add(new D(t,e,typeof t=="function"?e:n))}postMerge(t){return this.#t.postMerge(t),this}name(t){return this.#t.name(t),this}createUrlFunctions(t,e){return this.#t.createUrlFunctions(t,e),this}_envIsRequired=!1;_perEnvDsCount=null;addPerEnvironment(t){if(!this._envSource)throw new Error("Using addPerEnvironment() requires a prior call to includeEnvironment().");return this._envSource.environment.all.forEach(e=>{const n=t(this,e);n!==!1&&this.forEnvironment(e,typeof n=="string"?n:void 0)}),this}when(t,e){return this.#t.when(t,e),this}forEnvironment(t,e){this._envIsRequired=!0,this._perEnvDsCount=this._perEnvDsCount??{};let n=this._perEnvDsCount[t]??0;return this._perEnvDsCount[t]=++n,e=e??(n===1?`${t} (environment-specific)`:`${t} #${n} (environment-specific)`),this.when(i=>i?.current.name===t,e)}whenAllTraits(t,e){return this._envIsRequired=!0,this.when(n=>n.hasTraits(t)??!1,e)}whenAnyTrait(t,e){return this._envIsRequired=!0,this.when(n=>n.hasAnyTrait(t),e)}async build(t=!1,e=!0){if(this.#t._lastCallWasDsAdd=!1,this._envIsRequired&&!this._envSource)throw new Error('The used build steps include at least one step that requires environment information. Ensure you are using "includeEnvironment()" as part of the build chain.');if(this._perEnvDsCount){let s=0;for(const u in this._perEnvDsCount){if(!this._envSource.environment.all.includes(u))throw new Error(`The environment name "${u}" was used in a call to forEnvironment(), but said name is not part of the list of possible environment names.`);++s}if(e){const u=this._envSource.environment.all.length;if(s!==u)throw new Error(`Only ${s} environment(s) were configured using forEnvironment() out of a total of ${u} environment(s). Either complete the list or disable this check when calling build().`)}}const n=await this.#t.build(t,s=>s(this._envSource?.environment)),i=this._envSource.name??"environment";if(n[i]!==void 0)throw new Error(`Cannot use property name "${i}" for the environment object because it was defined for something else.`);return n[i]=this._envSource.environment,n}}class B{#t=new J;add(t){return this.#t.add(t),this}addObject(t){return this.add(new T(t))}addDictionary(t,e,n){return this.add(new w(t,e??":",n))}addEnvironment(t,e="OPT_"){return this.add(new y(t,e))}addFetched(t,e=!0,n,i){return this.add(new E(t,e,n,i))}addJson(t,e,n){return this.add(new _(t,e,n))}addSingleValue(t,e,n){return this.add(new D(t,e,typeof t=="function"?e:n))}postMerge(t){return this.#t.postMerge(t),this}name(t){return this.#t.name(t),this}when(t,e){return this.#t.when(t,e),this}includeEnvironment(t,e="environment"){this.#t._lastCallWasDsAdd=!1;const n={name:e,environment:t};return new V(n,this.#t)}createUrlFunctions(t,e){return this.#t.createUrlFunctions(t,e),this}build(t=!1){return this.#t.build(t,e=>e())}}class N extends Error{value;constructor(t,e){super(e??`The provided environment value "${t}" was not found among the provided list of environments.`),this.value=t}}class S{name;traits;constructor(t,e){this.name=t,this.traits=e??0}}function z(r){if(typeof r=="string")return new S(r);if(r==null)throw new TypeError("Environment cannot be null or undefined.");return r}function K(r){return r[0].toLocaleUpperCase()+r.slice(1)}function L(r,t){const e=z(t),n={get all(){return r},get current(){return e}};n.hasAnyTrait=h.bind(n),n.hasTraits=u.bind(n);let i=!1;if(n.all.forEach(o=>{n[`is${K(o)}`]=function(){return n.current.name===o},i=i||n.current.name===o}),!i)throw new N(n.current.name);return n;function s(o){if(typeof o=="number"&&typeof this.current.traits!="number")throw new TypeError("Cannot test a numeric trait against string traits.");if((typeof o=="string"||b(o)&&typeof o[0]=="string")&&typeof this.current.traits=="number")throw new TypeError("Cannot test string traits against a numeric trait.");return typeof o=="string"&&(o=[o]),o}function u(o){o=s.call(this,o);const a=d=>(this.current.traits&d)===d&&d>0,c=d=>{let l=!0;return d.forEach(j=>{l=l&&this.current.traits.includes(j)}),l};return typeof o=="number"?a(o):c(o)}function h(o){o=s.call(this,o);const a=d=>(this.current.traits&d)>0,c=d=>{for(let l of d)if(this.current.traits.includes(l))return!0;return!1};return typeof o=="number"?a(o):c(o)}}function G(){return new B}export{m as DataSource,w as DictionaryDataSource,y as EnvironmentDataSource,S as EnvironmentDefinition,E as FetchedDataSource,_ as JsonDataSource,T as ObjectDataSource,D as SingleValueDataSource,L as buildEnvironment,G as default}; //# sourceMappingURL=index.min.js.map