diff --git a/PublishNote.md b/PublishNote.md index ab9eba7..5487136 100644 --- a/PublishNote.md +++ b/PublishNote.md @@ -1,4 +1,8 @@ -## v2.0.0 Is Live! +## v3.0.0 + +Version 3.0.0 is a full re-write on the TypeScript side of the package. Its Intellisense is now fully accurate for +almost everything and anything. It also exports the various data source classes to allow inheritance or composition to +create other data sources. > Visit the [project's homepage](https://github.com/WJSoftware/wj-config) for the latest version of this README. --- diff --git a/README.md b/README.md index 32a1db5..7694eff 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ [![NPM](https://img.shields.io/npm/v/wj-config?style=plastic)](https://www.npmjs.com/package/wj-config) ![Latest Release](https://img.shields.io/github/v/release/WJSoftware/wj-config?include_prereleases&sort=semver&style=plastic) -![Lines of code](https://img.shields.io/tokei/lines/github/WJSoftware/wj-config?style=plastic&color=blueviolet) +![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/WJSoftware/wj-config?style=plastic&color=violet) ![npm bundle size](https://img.shields.io/bundlephobia/min/wj-config?color=red&label=minified&style=plastic) > JavaScript configuration module for **NodeJS** and **browser frameworks** that works like ASP.net configuration -> where any number of data sources are merged and environment variables can contribute/overwrite values by following a ->naming convention. +> where any number of data sources are merged, and environment variables can contribute/overwrite values by following a +> naming convention. -Welcome to **wj-config**. This JavaScript configuration library works everywhere, most likely. The table below shows -the frameworks or libraries that have successful samples in the [examples](https://github.com/WJSoftware/wj-config/tree/main/examples) folder in the left column. The -right column is pretty much anything else out there that looks like it supports **ES Modules**. +This JavaScript configuration library works everywhere, most likely. The table below shows the frameworks or libraries +that have successful samples in the [examples](https://github.com/WJSoftware/wj-config/tree/main/examples) folder in the +left column. The right column is pretty much anything else out there that looks like it supports **ES Modules**. @@ -26,7 +26,7 @@ right column is pretty much anything else out there that looks like it supports JavaScript JavaScript
TypeScript TypeScript
NodeJS NodeJS
- Deno Deno
+ Deno Deno
ReactJS ReactJS
VueJS VueJS
Svelte Svelte @@ -37,11 +37,12 @@ right column is pretty much anything else out there that looks like it supports Electron Electron
Angular Angular
Remix Remix
- EmberJS EmberJS
+ EmberJS EmberJS
SennaJS SennaJS
MithrilJS MithrilJS
SlingJS SlingJS
Lit Lit
+ Bun Bun
@@ -145,7 +146,7 @@ Example configuration JSON: > **NOTE**: The `ws` section is special. See [URL-Building Functions](https://github.com/WJSoftware/wj-config/wiki/English__Theory__URL-Building-Functions) > in the **Wiki** for the details. -Now write per-configuration JSON files. Example for development (would be named `config.Development.json`): +Now write per-environment JSON files. Example for development (would be named `config.Development.json`): ```json { @@ -155,8 +156,8 @@ Now write per-configuration JSON files. Example for development (would be named } ``` -Yes, you only write the overrides, the values that change for the environment. All other configuration is still -available, but does not have to be repeated. +Yes, you only write the overrides, the values that change for the environment. All other configuration values will also +be available, but is not necessary to repeat them: DRY configuration. ### 3. Build Your Configuration Object @@ -167,10 +168,10 @@ There are two styles available: The *classic* style leaves to you, the programm a way to select the correct per-environment data source. The *conditional* style leaves the decision to the configuration builder. Pick whichever pleases you, but know that the latter is safer. -From now on, any code samples that call the `loadJsonFile()` function are referring to this one: +From now on, any code samples that call the `loadJsonFile()` function are referring to this function: ```js -const loadJsonFile = (fileName, isRequired) => { +function loadJsonFile(fileName, isRequired) { const fileExists = fs.existsSync(fileName); if (fileExists) { const data = fs.readFileSync(fileName); @@ -184,19 +185,19 @@ const loadJsonFile = (fileName, isRequired) => { }; ``` -If you don't like it, feel free to write your own. I wrote this like a year ago; I had no knowledge of the existence -of the `fs/promises` module. If you write one yourself using async `fs`, please pull request and share the love. 😁😎 +If you don't like it, feel free to write your own. I wrote this before I knew of the existence of the `fs/promises` +module. If you write one yourself using async `fs`, please pull request and share the love. 😁😎 #### Classic Style ##### NodeJS ES Modules (Recommended) ```js -import wjConfig, { Environment } from 'wj-config'; +import wjConfig, { buildEnvironment } from 'wj-config'; import mainConfig from "./config.json" assert {type: 'json'}; // Importing data is a thing in NodeJS. // Obtain an environment object ahead of time to help setting configuration up. -const env = new Environment(process.env.NODE_ENV); +const env = buildEnvironment(process.env.NODE_ENV /*, ['my', 'own', 'environment', 'list'] */); const configPromise = wjConfig() .addObject(mainConfig) // Main configuration JSON file. @@ -205,7 +206,7 @@ const configPromise = wjConfig() .name(env.current.name) .addEnvironment(process.env) // Adds a data source that reads the environment variables in process.env. .includeEnvironment(env) // So the final configuration object has the environment property. - .createUrlFunctions() // So the final configuration object will contain URL builder functions. + .createUrlFunctions('ws') // So the final configuration object will contain URL builder functions. .build(env.isDevelopment()); // Only trace configuration values in the Development environment. // This is a top-level await: @@ -224,8 +225,8 @@ use of [URL-Building Functions](https://github.com/WJSoftware/wj-config/wiki/Eng // whole thing within a call to .then(), like in one of the examples provided in this project's repository. // This is why CommonJS is discouraged. It makes things more complex. module.exports = (async function () { - const { default: wjConfig, Environment } = await import('wj-config'); - const env = new Environment(process.env.NODE_ENV); + const { default: wjConfig, buildEnvironment } = await import('wj-config'); + const env = buildEnvironment(process.env.NODE_ENV /*, ['my', 'own', 'environment', 'list'] */); return wjConfig() .addObject(loadJsonFile('./config.json', true)) .name('Main') @@ -233,24 +234,24 @@ module.exports = (async function () { .name(env.current.name) .addEnvironment(process.env) .includeEnvironment(env) - .createUrlFunctions() + .createUrlFunctions('ws') .build(env.isDevelopment()); })(); ``` ##### Web Projects -> **IMPORTANT**: If your project is a React project creasted with *Create React App*, the recommendation is to eject +> **IMPORTANT**: If your project is a React project created with *Create React App*, the recommendation is to eject > or use the `@craco/craco` package (or similar one) in order to configure webpack to allow top-level awaits. You > can read the details in the [Top Level Await](https://github.com/WJSoftware/wj-config/wiki/English__JavaScript-Concepts__Top-Level-Await) > section in the **Wiki**. It can also work without top-level awaits, but in all honesty, I don't like it. The > **Wiki** also explains how to achieve this for Vite projects (Vue, Svelte, React, etc.). ```js -import wjConfig, { Environment } from 'wj-config'; +import wjConfig, { buildEnvironment } from 'wj-config'; import mainConfig from './config.json'; // One may import data like this, or fetch it. -const env = new Environment(window.env.REACT_ENVIRONMENT); +const env = buildEnvironment(window.env.REACT_ENVIRONMENT /*, ['my', 'own', 'environment', 'list'] */); const configPromise = wjConfig() .addObject(mainConfig) .name('Main') // Give data sources a meaningful name for value tracing purposes. @@ -258,7 +259,7 @@ const configPromise = wjConfig() .name(env.current.name) .addEnvironment(window.env, 'REACT_APP_') // Adds a data source that reads the environment variables in window.env. .includeEnvironment(env) // So the final configuration object has the environment property. - .createUrlFunctions() // So the final configuration object will contain URL builder functions. + .createUrlFunctions('ws') // So the final configuration object will contain URL builder functions. .build(env.isDevelopment()); // Only trace configuration values in the Development environment. export default await configPromise; @@ -302,17 +303,17 @@ There are two possible ways to do conditional style per-environment configuratio **Web Projects** sample: ```javascript -import wjConfig, { Environment } from 'wj-config'; +import wjConfig, { buildEnvironment } from 'wj-config'; import mainConfig from './config.json'; -const env = new Environment(window.env.REACT_ENVIRONMENT); +const env = buildEnvironment(window.env.REACT_ENVIRONMENT /*, ['my', 'own', 'environment', 'list'] */); const config = wjConfig() .addObject(mainConfig) .name('Main') .includeEnvironment(env) .addPerEnvironment((b, envName) => b.addFetched(`/config.${envName}.json`, false)) .addEnvironment(window.env, 'REACT_APP_') - .createUrlFunctions() + .createUrlFunctions('ws') .build(env.isDevelopment()); export default await config; @@ -325,10 +326,10 @@ It looks almost identical to the classic. This one has a few advantages: 3. Makes sure there's at least one data source per defined environment. **IMPORTANT**: This conditional style requires the call to `includeEnvironment()` and to be made *before* calling -`addPerEnvironment()`. Make sure you define your environment names when creating the `Environment` object: +`addPerEnvironment()`. Make sure you define your environment names when creating the environment object: ```javascript -const env = new Environment(window.env.REACT_ENVIRONMENT, ['myDev', 'myTest', 'myProd']); +const env = buildEnvironment(window.env.REACT_ENVIRONMENT, ['myDev', 'myTest', 'myProd']); ``` This way `addPerEnvironment()` knows your environment names. @@ -336,10 +337,10 @@ This way `addPerEnvironment()` knows your environment names. The longer way of the conditional style looks like this: ```javascript -import wjConfig, { Environment } from 'wj-config'; +import wjConfig, { buildEnvironment } from 'wj-config'; import mainConfig from './config.json'; -const env = new Environment(window.env.REACT_ENVIRONMENT); +const env = buildEnvironment(window.env.REACT_ENVIRONMENT); const config = wjConfig() .addObject(mainConfig) .name('Main') @@ -351,12 +352,14 @@ const config = wjConfig() .forEnvironment('Production') .addEnvironment(window.env, 'REACT_APP_') .includeEnvironment(env) - .createUrlFunctions() + .createUrlFunctions('ws') .build(env.isDevelopment()); export default await config; ``` +> When not specified, the list of environments is `'Development'`, `'PreProduction'`, and `'Production'`. + This one has advantages 2 and 3 above, plus allows for the possiblity of having completely different data source types per environment. Furthermore, this allows you to add more environment-specific data sources if, for example, a particular environment requires 2 or more data sources. 95% of the time you'll need the short one only. @@ -368,10 +371,10 @@ This works in **NodeJS** too. There is a performance catch, though: If in Node ones. To avoid this performance hit, pass a function to `addObject()` that, in turn, calls `loadJsonFile()`: ```js -import wjConfig, { Environment } from 'wj-config'; +import wjConfig, { buildEnvironment } from 'wj-config'; import mainConfig from "./config.json" assert {type: 'json'}; -const env = new Environment(process.env.NODE_ENV); +const env = buildEnvironment(process.env.NODE_ENV); const config = wjConfig() .addObject(mainConfig) @@ -380,7 +383,7 @@ const config = wjConfig() // Using a function that calls loadJsonFile() instead of calling loadJsonFile directly. .addPerEnvironment((b, envName) => b.addObject(() => loadJsonFile(`./config.${envName}.json`))) .addEnvironment(process.env) - .createUrlFunctions() + .createUrlFunctions('ws') .build(env.isDevelopment()); export default await config; @@ -391,8 +394,7 @@ Now you know how to do per-environment configuration in the *classic* and *condi ## Documentation This README was already too long, so all documentation has been re-written and placed in this repository's -[wiki](https://github.com/WJSoftware/wj-config/wiki). It is in English only for now; it should be available in -Spanish too within the next few months. +[wiki](https://github.com/WJSoftware/wj-config/wiki). It is in English only for now. Be sure to stop by because this not-so-quick start tutorial only scratched the surface of what is possible with **wj-config**. diff --git a/package-lock.json b/package-lock.json index 34b99a6..3e5a8e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,213 @@ { "name": "wj-config", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "typescript": "^5.2.2" + "publint": "^0.2.11", + "typescript": "^5.6.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/publint": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.12.tgz", + "integrity": "sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "npm-packlist": "^5.1.3", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "lib/cli.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20,14 +215,13 @@ "engines": { "node": ">=14.17" } - } - }, - "dependencies": { - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" } } } diff --git a/package.json b/package.json index 04263dc..36c6646 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { - "typescript": "^5.2.2" + "publint": "^0.2.11", + "typescript": "^5.6.2" } } diff --git a/src/Builder.ts b/src/Builder.ts deleted file mode 100644 index 7fade03..0000000 --- a/src/Builder.ts +++ /dev/null @@ -1,276 +0,0 @@ -import type { ConfigurationValue, IBuilder, IConfig, ICoreConfig, IDataSource, IEnvironment, Predicate, ProcessFetchResponse, Traits } from "wj-config"; -import DictionaryDataSource from "./DictionaryDataSource.js" -import { Environment } from "./Environment.js" -import EnvironmentDataSource from "./EnvironmentDataSource.js" -import FetchedDataSource from "./FetchedDataSource.js"; -import { isConfig } from "./helpers.js" -import JsonDataSource from "./JsonDataSource.js"; -import makeWsUrlFunctions from "./makeWsUrlFunctions.js" -import merge from "./Merge.js" -import { ObjectDataSource } from "./ObjectDataSource.js" -import SingleValueDataSource from "./SingleValueDataSource.js"; - -interface IEnvironmentSource { - name?: string, - environment: IEnvironment -} - -interface IUrlData { - wsPropertyNames: string[]; - routeValuesRegExp: RegExp; -} - -interface IDataSourceDef { - dataSource: IDataSource, - predicate?: Predicate -} - -export default class Builder implements IBuilder { - /** - * Default list of property names that undergo the URL functions transformation. - */ - static readonly defaultWsPropertyNames = ['ws']; - - /** - * Collection of data sources added to the builder. - */ - private _dsDefs: IDataSourceDef[] = []; - - /** - * Environment source. - */ - private _envSource?: IEnvironmentSource; - - /** - * Boolean flag used to raise an error if there was no call to includeEnvironment() when it is known to be needed. - */ - private _envIsRequired: boolean = false; - - /** - * Dictionary of environment names that have been configured with a data source using the addPerEnvironment() - * helper function. The value is the number of times the environment name has been used. - */ - private _perEnvDsCount: { [x: string]: number } | null = null; - - /** - * URL data used to create URL functions out of specific property values in the resulting configuration object. - */ - private _urlData?: IUrlData; - - /** - * Flag to determine if the last call in the builder was the addition of a data source. - */ - private _lastCallWasDsAdd: boolean = false; - - add(dataSource: IDataSource): IBuilder { - this._dsDefs.push({ - dataSource: dataSource - }); - dataSource.index = this._dsDefs.length - 1; - this._lastCallWasDsAdd = true; - return this; - } - - addObject(obj: ICoreConfig | (() => Promise)): IBuilder { - return this.add(new ObjectDataSource(obj)); - } - - addDictionary(dictionary: ICoreConfig | (() => Promise), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate): IBuilder { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); - } - - addEnvironment(env: ICoreConfig | (() => Promise), prefix: string = 'OPT_'): IBuilder { - return this.add(new EnvironmentDataSource(env, prefix)); - } - - addFetched(input: URL | RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, procesFn?: ProcessFetchResponse): IBuilder { - return this.add(new FetchedDataSource(input, required, init, procesFn)); - } - - addJson(json: string | (() => Promise), jsonParser?: JSON, reviver?: (this: any, key: string, value: any) => any) { - return this.add(new JsonDataSource(json, jsonParser, reviver)); - } - - addSingleValue(path: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string): IBuilder { - return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); - } - - addPerEnvironment(addDs: (builder: IBuilder, envName: string) => boolean | string): IBuilder { - if (!this._envSource) { - throw new Error('Using addPerEnvironment() requires a prior call to includeEnvironment().'); - } - this._envSource.environment.all.forEach(n => { - const result = addDs(this, n); - if (result !== false) { - this.forEnvironment(n, typeof result === 'string' ? result : undefined); - } - }); - return this; - } - - name(name: string): IBuilder { - 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; - return this; - } - - when(predicate: Predicate, dataSourceName?: string): IBuilder { - 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); - } - return this; - } - - whenAllTraits(traits: Traits, dataSourceName?: string): IBuilder { - this._envIsRequired = true; - return this.when(env => { - return (env as IEnvironment).hasTraits(traits); - }, dataSourceName); - } - - whenAnyTrait(traits: Traits, dataSourceName?: string): IBuilder { - this._envIsRequired = true; - return this.when(env => { - return (env as IEnvironment).hasAnyTrait(traits); - }, dataSourceName); - } - - forEnvironment(envName: string, dataSourceName?: string): IBuilder { - this._envIsRequired = true; - this._perEnvDsCount = this._perEnvDsCount ?? {}; - let count = this._perEnvDsCount[envName] ?? 0; - this._perEnvDsCount[envName] = ++count; - dataSourceName = - dataSourceName ?? - (count === 1 ? `${envName} (environment-specific)` : `${envName} #${count} (environment-specific)`); - return this.when(e => e?.current.name === envName, dataSourceName); - } - - includeEnvironment(valueOrEnv: string | IEnvironment, propNameOrEnvNames?: string[] | string, propertyName?: string): IBuilder { - this._lastCallWasDsAdd = false; - const propName = typeof propNameOrEnvNames === 'string' ? propNameOrEnvNames : propertyName; - const envNames = propNameOrEnvNames && typeof propNameOrEnvNames !== 'string' ? propNameOrEnvNames : Environment.defaultNames; - let env: IEnvironment | undefined = undefined; - if (typeof valueOrEnv === 'string') { - env = new Environment(valueOrEnv, envNames); - } - else { - env = valueOrEnv; - } - this._envSource = { - name: propName, - environment: env - }; - return this; - } - - createUrlFunctions(wsPropertyNames?: string | string[], routeValuesRegExp?: RegExp): IBuilder { - this._lastCallWasDsAdd = false; - let propNames = null; - if (typeof wsPropertyNames === 'string') { - if (wsPropertyNames !== '') { - propNames = [wsPropertyNames]; - } - } - else if (wsPropertyNames && wsPropertyNames.length > 0) { - propNames = wsPropertyNames - } - else { - propNames = Builder.defaultWsPropertyNames; - } - this._urlData = { - wsPropertyNames: propNames as string[], - routeValuesRegExp: routeValuesRegExp ?? /\{(\w+)\}/g - }; - return this; - } - - async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true): Promise { - this._lastCallWasDsAdd = false; - // See if environment is required. - if (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.'); - } - // See if forEnvironment was used. - if (this._perEnvDsCount) { - // Ensure all specified environments are part of the possible list of environments. - let envCount = 0; - for (const e in this._perEnvDsCount) { - if (!(this._envSource as IEnvironmentSource).environment.all.includes(e)) { - throw new Error(`The environment name "${e}" was used in a call to forEnvironment(), but said name is not part of the list of possible environment names.`); - } - ++envCount; - } - if (enforcePerEnvironmentCoverage) { - // Ensure all possible environment names were included. - const totalEnvs = (this._envSource as IEnvironmentSource).environment.all.length; - if (envCount !== totalEnvs) { - throw new Error(`Only ${envCount} environment(s) were configured using forEnvironment() out of a total of ${totalEnvs} environment(s). Either complete the list or disable this check when calling build().`); - } - } - } - const qualifyingDs: IDataSource[] = []; - let wjConfig: ICoreConfig; - 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 || ds.predicate(this._envSource?.environment)) { - qualifyingDs.push(ds.dataSource); - } - }); - if (qualifyingDs.length > 0) { - const dsTasks: Promise[] = []; - qualifyingDs.forEach(ds => { - dsTasks.push(ds.getObject()); - }); - const sources = await Promise.all(dsTasks); - wjConfig = merge(sources, traceValueSources ? qualifyingDs : undefined); - } - else { - wjConfig = {}; - } - } - else { - wjConfig = {}; - } - if (this._envSource) { - const envPropertyName = this._envSource.name ?? 'environment'; - if (wjConfig[envPropertyName] !== undefined) { - throw new Error(`Cannot use property name "${envPropertyName}" for the environment object because it was defined for something else.`); - } - wjConfig[envPropertyName] = this._envSource.environment; - } - const urlData = this._urlData; - if (urlData) { - urlData.wsPropertyNames.forEach((value) => { - const obj = wjConfig[value]; - if (isConfig(obj)) { - makeWsUrlFunctions(obj, 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 = []; - } - } - return wjConfig; - } -}; diff --git a/src/DictionaryDataSource.ts b/src/DictionaryDataSource.ts deleted file mode 100644 index 2a8bcf0..0000000 --- a/src/DictionaryDataSource.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { ConfigurationValue, ICoreConfig, IDataSource, Predicate } from "wj-config"; -import { DataSource } from "./DataSource.js"; -import { attemptParse, forEachProperty, isConfig } from "./helpers.js"; - -const processKey = (key: string, hierarchySeparator: string, prefix?: string) => { - if (prefix) { - key = key.substring(prefix.length); - } - return key.split(hierarchySeparator); -}; - -const ensurePropertyValue = (obj: ICoreConfig, name: string) => { - if (obj[name] === undefined) { - obj[name] = {}; - } - return obj[name]; -} - -export default class DictionaryDataSource extends DataSource implements IDataSource { - private _dictionary: ICoreConfig | (() => Promise); - private _hierarchySeparator: string; - private _prefixOrPredicate?: string | Predicate; - - #buildPredicate(): [Predicate, string] { - let predicateFn: Predicate = name => true; - let prefix: string = ''; - if (this._prefixOrPredicate) { - if (typeof this._prefixOrPredicate === "string") { - prefix = this._prefixOrPredicate; - predicateFn = name => name.startsWith(prefix); - } - else { - predicateFn = this._prefixOrPredicate; - } - } - return [predicateFn, prefix]; - } - - #validateDictionary(dic: ICoreConfig) { - if (!isConfig(dic)) { - throw new Error('The provided dictionary must be a flat object.'); - } - const [predicateFn, prefix] = this.#buildPredicate(); - forEachProperty(dic, (k, v) => { - if (!predicateFn(k)) { - // This property does not qualify, so skip its validation. - return false; - } - if (isConfig(v)) { - throw new Error(`The provided dictionary must be a flat object: Property ${k} has a non-scalar value.`); - } - }); - } - - #inflateDictionary(dic: ICoreConfig) { - const result: ICoreConfig = {}; - if (!dic || !isConfig(dic)) { - return result; - } - const [predicateFn, prefix] = this.#buildPredicate(); - forEachProperty(dic, (key, value) => { - if (predicateFn(key)) { - // Object values are disallowed because a dictionary's source is assumed to be flat. - if (isConfig(value)) { - throw new Error(`Dictionary data sources cannot hold object values. Key: ${key}`); - } - const keyParts = processKey(key, this._hierarchySeparator, prefix); - let obj: ConfigurationValue = result; - for (let i = 0; i < keyParts.length - 1; ++i) { - obj = ensurePropertyValue(obj as ICoreConfig, keyParts[i]); - if (!isConfig(obj)) { - throw new Error(`Cannot set the value of variable "${key}" because "${keyParts[i]}" has already been created as a leaf value.`); - } - } - // Ensure there is no value override. - if ((obj as ICoreConfig)[keyParts[keyParts.length - 1]]) { - throw new Error(`Cannot set the value of variable "${key}" because "${keyParts[keyParts.length - 1]}" has already been created as an object to hold other values.`); - } - // If the value is a string, attempt parsing. This is to support data sources that can only hold strings - // as values, such as enumerating actual system environment variables. - if (typeof value === 'string') { - value = attemptParse(value); - } - (obj as ICoreConfig)[keyParts[keyParts.length - 1]] = value; - } - }); - return result; - } - - constructor(dictionary: ICoreConfig | (() => Promise), hierarchySeparator: string, prefixOrPredicate?: string | Predicate) { - super('Dictionary'); - if (!hierarchySeparator) { - throw new Error('Dictionaries must specify a hierarchy separator.'); - } - if (typeof hierarchySeparator !== 'string') { - throw new Error('The hierarchy separator must be a string.'); - } - this._hierarchySeparator = hierarchySeparator; - if (typeof prefixOrPredicate === 'string' && prefixOrPredicate.length === 0) { - throw new Error('The provided prefix value cannot be an empty string.'); - } - this._prefixOrPredicate = prefixOrPredicate; - if (dictionary && typeof dictionary !== 'function') { - this.#validateDictionary(dictionary); - } - this._dictionary = dictionary; - } - - async getObject(): Promise { - let dic = this._dictionary; - if (dic && typeof dic === 'function') { - dic = await dic(); - } - const inflatedObject = this.#inflateDictionary(dic); - return Promise.resolve(inflatedObject); - } -} diff --git a/src/Environment.ts b/src/Environment.ts deleted file mode 100644 index 868db1a..0000000 --- a/src/Environment.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { EnvironmentTest, IEnvironment, IEnvironmentDefinition, Trait, Traits } from "wj-config"; -import { InvalidEnvNameError } from "./EnvConfigError.js"; -import { EnvironmentDefinition } from "./EnvironmentDefinition.js"; -import { isArray } from "./helpers.js"; - -function ensureEnvDefinition(env: string | IEnvironmentDefinition): IEnvironmentDefinition { - if (typeof env === 'string') { - return new EnvironmentDefinition(env); - } - return env; -} - -export class Environment implements IEnvironment { - /** - * Default list of environment names. - */ - static defaultNames: string[] = ['Development', 'PreProduction', 'Production']; - - readonly current: IEnvironmentDefinition; - readonly all: string[]; - [x: string | 'current' | 'all' | 'hasTraits' | 'hasAnyTrait']: EnvironmentTest | IEnvironmentDefinition | string[] | ((traits: Traits) => boolean) - - constructor(currentEnvironment: string | IEnvironmentDefinition, possibleEnvironments?: string[]) { - this.all = possibleEnvironments ?? Environment.defaultNames; - this.current = ensureEnvDefinition(currentEnvironment); - let validCurrentEnvironment = false; - this.all.forEach((envName) => { - (this as unknown as { [x: string]: () => boolean })[`is${envName}`] = function () { return (this as unknown as Environment).current.name === envName; }; - validCurrentEnvironment = validCurrentEnvironment || this.current.name === envName; - }); - // Throw if the current environment name was not found among the possible environment names.. - if (!validCurrentEnvironment) { - throw new InvalidEnvNameError(this.current.name); - } - } - - #normalizeTestTraits(traits: Trait | Traits): Trait | Traits { - if (typeof traits === 'number' && typeof this.current.traits !== 'number') { - throw new TypeError('Cannot test a numeric trait against string traits.'); - } - if ((typeof traits === 'string' || (isArray(traits) && typeof traits[0] === 'string')) && typeof this.current.traits === 'number') { - throw new TypeError('Cannot test string traits against a numeric trait.'); - } - if (typeof traits === 'string') { - traits = [traits]; - } - return traits; - } - - hasTraits(traits: Trait | Traits): boolean { - traits = this.#normalizeTestTraits(traits); - const hasBitwiseTraits = (t: number) => ((this.current.traits as number) & t) === t && t > 0; - const hasStringTraits = (t: string[]) => { - let has = true; - t.forEach(it => { - has = has && (this.current.traits as string[]).includes(it); - }); - return has; - }; - if (typeof traits === "number") { - return hasBitwiseTraits(traits); - } - return hasStringTraits(traits as string[]); - } - - hasAnyTrait(traits: Trait | Traits): boolean { - traits = this.#normalizeTestTraits(traits); - const hasAnyBitwiseTrait = (t: number) => ((this.current.traits as number) & t) > 0; - const hasAnyStringTrait = (t: string[]) => { - for (let x of t) { - if ((this.current.traits as string[]).includes(x)) { - return true; - } - } - return false; - }; - if (typeof traits === "number") { - return hasAnyBitwiseTrait(traits); - } - return hasAnyStringTrait(traits as string[]); - } -} diff --git a/src/EnvironmentDataSource.ts b/src/EnvironmentDataSource.ts deleted file mode 100644 index 1f3c7c1..0000000 --- a/src/EnvironmentDataSource.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ICoreConfig } from "wj-config"; -import DictionaryDataSource from "./DictionaryDataSource.js"; - -export default class EnvironmentDataSource extends DictionaryDataSource { - constructor(env: ICoreConfig | (() => Promise), prefix?: string) { - super(env, '__', prefix); - if (!prefix) { - throw new Error('The prefix is mandatory to avoid accidental imports of sensitive data from environment variable values.'); - } - this.name = 'Environment'; - } -} diff --git a/src/EnvironmentDefinition.ts b/src/EnvironmentDefinition.ts index 7c954e1..c417b1c 100644 --- a/src/EnvironmentDefinition.ts +++ b/src/EnvironmentDefinition.ts @@ -1,10 +1,24 @@ -import type { IEnvironmentDefinition, Traits } from "wj-config"; +import type { IEnvironmentDefinition, Traits } from "./wj-config.js"; -export class EnvironmentDefinition implements IEnvironmentDefinition { - public readonly name: string; +/** + * Environment definition class used to specify the current environment as an object. + */ +export class EnvironmentDefinition implements IEnvironmentDefinition { + /** + * Gets the environment's name. + */ + public readonly name: TEnvironments; + /** + * Gets the environment's assigned traits. + */ public readonly traits: Traits; - constructor(name: string, traits?: Traits) { + /** + * Initializes a new instance of this class. + * @param name The name of the current environment. + * @param traits The traits assigned to the current environment. + */ + constructor(name: TEnvironments, traits?: Traits) { this.name = name; this.traits = traits ?? 0; } diff --git a/src/Merge.ts b/src/Merge.ts index 10eb403..83c4f74 100644 --- a/src/Merge.ts +++ b/src/Merge.ts @@ -1,12 +1,13 @@ -import type { ICoreConfig, IDataSource } from "wj-config"; -import { forEachProperty, isArray, isConfig } from "./helpers.js"; +import { forEachProperty, isArray, isConfigNode } from "./helpers.js"; +import type { ConfigurationNode, IDataSource, Trace } from "./wj-config.js"; + type TraceRequest = { - trace: ICoreConfig, + trace: Trace, dataSource: IDataSource } -function mergeTwo(obj1: ICoreConfig, obj2: ICoreConfig, trace?: TraceRequest) { +function mergeTwo(obj1: ConfigurationNode, obj2: ConfigurationNode, trace?: TraceRequest) { let recursiveTrace: TraceRequest | undefined; // Add the properties of obj2. forEachProperty(obj2, (key, value) => { @@ -14,17 +15,17 @@ function mergeTwo(obj1: ICoreConfig, obj2: ICoreConfig, trace?: TraceRequest) { if (value1 !== undefined) { // If it is a scalar/array value, the value in object 2 must also be a scalar or array. // If it is an object value, then value in object 2 must also be an object. - if (isConfig(value1) && !isConfig(value)) { + if (isConfigNode(value1) && !isConfigNode(value)) { throw new Error(`The destination value of property "${key}" is an object, but the second object is not providing an object value.`); } - if (!isConfig(value1) && isConfig(value)) { + if (!isConfigNode(value1) && isConfigNode(value)) { throw new Error(`The destination value of property "${key}" is a scalar/array value, but the second object is not providing a scalar/array value.`); } - if (isConfig(value1)) { + if (isConfigNode(value1)) { // Recursively merge obj2 into obj1. if (trace) { recursiveTrace = { - trace: trace.trace[key] = (trace.trace[key] as ICoreConfig) ?? {}, + trace: (trace.trace[key] = trace.trace[key] ?? {}) as Trace, dataSource: trace.dataSource } } @@ -38,18 +39,18 @@ function mergeTwo(obj1: ICoreConfig, obj2: ICoreConfig, trace?: TraceRequest) { } } else { - if (trace && isConfig(value)) { + if (trace && isConfigNode(value)) { // Must trace, so merge. obj1[key] = {}; recursiveTrace = { - trace: trace.trace[key] = (trace.trace[key] as ICoreConfig) ?? {}, + trace: (trace.trace[key] = trace.trace[key] ?? {}) as Trace, dataSource: trace.dataSource }; - mergeTwo((obj1[key] as ICoreConfig), value, recursiveTrace); + mergeTwo((obj1[key] as ConfigurationNode), value, recursiveTrace); } else { obj1[key] = value; - if (!isConfig(value) && trace) { + if (!isConfigNode(value) && trace) { // Update the trace. trace.trace[key] = trace.dataSource.trace(); } @@ -59,19 +60,19 @@ function mergeTwo(obj1: ICoreConfig, obj2: ICoreConfig, trace?: TraceRequest) { return obj1; } -export default function merge(objects: ICoreConfig[], dataSources?: IDataSource[]): ICoreConfig { +export default function merge(objects: ConfigurationNode[], dataSources?: IDataSource[]): ConfigurationNode & { _trace?: Trace; } { if (!isArray(objects)) { throw new Error('The provided value is not an array of objects.'); } // There must be at least one object. - if (objects.length === 0 || !isConfig(objects[0]) || objects[0] === null || objects[0] === undefined) { + if (objects.length === 0 || !isConfigNode(objects[0]) || objects[0] === null || objects[0] === undefined) { throw new Error('The first element of the array is required and must be a suitable configuration object.'); } // If there are data sources, the number of these must match the number of provided objects. if (dataSources && objects.length !== dataSources?.length) { throw new Error('The number of provided objects differs from the number of provided data sources.'); } - let result: ICoreConfig = objects[0]; + let result: ConfigurationNode = objects[0]; let initialIndex = 1; let trace: TraceRequest | undefined; if (dataSources) { @@ -88,7 +89,7 @@ export default function merge(objects: ICoreConfig[], dataSources?: IDataSource[ if (nextObject === null || nextObject === undefined) { nextObject = {}; } - if (!isConfig(nextObject)) { + if (!isConfigNode(nextObject)) { throw new Error(`Configuration object at index ${idx} is not of the appropriate type.`); } if (trace) { @@ -97,7 +98,7 @@ export default function merge(objects: ICoreConfig[], dataSources?: IDataSource[ mergeTwo(result, nextObject, trace); } if (trace) { - result._trace = trace.trace; + (result as Trace)._trace = trace.trace; } return result; } \ No newline at end of file diff --git a/src/buildEnvironment.ts b/src/buildEnvironment.ts new file mode 100644 index 0000000..8f1714d --- /dev/null +++ b/src/buildEnvironment.ts @@ -0,0 +1,92 @@ +import { InvalidEnvNameError } from "./EnvConfigError.js"; +import { EnvironmentDefinition } from "./EnvironmentDefinition.js"; +import { isArray } from "./helpers.js"; +import type { EnvironmentTestFn, IEnvironment, IEnvironmentDefinition, Trait, Traits } from "./wj-config.js"; + +function ensureEnvDefinition(env: string | IEnvironmentDefinition): IEnvironmentDefinition { + if (typeof env === 'string') { + return new EnvironmentDefinition(env) as IEnvironmentDefinition; + } + return env; +} + +function capitalize(text: string) { + return text[0].toLocaleUpperCase() + text.slice(1); +} + +/** + * Builds an environment object with the provided environment information. + * @param currentEnvironment The application's current environment. + * @param possibleEnvironments The complete list of all possible environments. + * @returns The newly created `IEnvironment` object. + */ +export function buildEnvironment( + currentEnvironment: TEnvironments | IEnvironmentDefinition, + possibleEnvironments?: TEnvironments[] +): IEnvironment { + const defaultNames: string[] = ['Development', 'PreProduction', 'Production']; + const env = { + all: possibleEnvironments ?? defaultNames, + current: ensureEnvDefinition(currentEnvironment), + } as IEnvironment; + env.hasAnyTrait = hasAnyTrait.bind(env); + env.hasTraits = hasTraits.bind(env); + let validCurrentEnvironment = false; + env.all.forEach((envName) => { + (env as Record)[`is${capitalize(envName)}`] = function () { + return env.current.name === envName; + }; + validCurrentEnvironment = validCurrentEnvironment || env.current.name === envName; + }); + // Throw if the current environment name was not found among the possible environment names.. + if (!validCurrentEnvironment) { + throw new InvalidEnvNameError(env.current.name); + } + return env; + + function normalizeTestTraits(this: IEnvironment, traits: Trait | Traits): Trait | Traits { + if (typeof traits === 'number' && typeof this.current.traits !== 'number') { + throw new TypeError('Cannot test a numeric trait against string traits.'); + } + if ((typeof traits === 'string' || (isArray(traits) && typeof traits[0] === 'string')) && typeof this.current.traits === 'number') { + throw new TypeError('Cannot test string traits against a numeric trait.'); + } + if (typeof traits === 'string') { + traits = [traits]; + } + return traits; + } + + function hasTraits(this: IEnvironment, traits: Trait | Traits): boolean { + traits = normalizeTestTraits.call(this, traits); + const hasBitwiseTraits = (t: number) => ((this.current.traits as number) & t) === t && t > 0; + const hasStringTraits = (t: string[]) => { + let has = true; + t.forEach(it => { + has = has && (this.current.traits as string[]).includes(it); + }); + return has; + }; + if (typeof traits === "number") { + return hasBitwiseTraits(traits); + } + return hasStringTraits(traits as string[]); + } + + function hasAnyTrait(this: IEnvironment, traits: Trait | Traits): boolean { + traits = normalizeTestTraits.call(this, traits); + const hasAnyBitwiseTrait = (t: number) => ((this.current.traits as number) & t) > 0; + const hasAnyStringTrait = (t: string[]) => { + for (let x of t) { + if ((this.current.traits as string[]).includes(x)) { + return true; + } + } + return false; + }; + if (typeof traits === "number") { + return hasAnyBitwiseTrait(traits); + } + return hasAnyStringTrait(traits as string[]); + } +} diff --git a/src/builders/Builder.ts b/src/builders/Builder.ts new file mode 100644 index 0000000..47fda39 --- /dev/null +++ b/src/builders/Builder.ts @@ -0,0 +1,84 @@ +import { buildEnvironment } from "../buildEnvironment.js"; +import { DictionaryDataSource } from "../dataSources/DictionaryDataSource.js"; +import { EnvironmentDataSource } from "../dataSources/EnvironmentDataSource.js"; +import { FetchedDataSource } from "../dataSources/FetchedDataSource.js"; +import { JsonDataSource } from "../dataSources/JsonDataSource.js"; +import { ObjectDataSource } from "../dataSources/ObjectDataSource.js"; +import { SingleValueDataSource } from "../dataSources/SingleValueDataSource.js"; +import type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, InflateDictionary, InflateKey, MergeResult, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "../wj-config.js"; +import { BuilderImpl } from "./BuilderImpl.js"; +import { EnvAwareBuilder, type IEnvironmentSource } from "./EnvAwareBuilder.js"; + +export class Builder = {}> implements IBuilder { + #impl: BuilderImpl = new BuilderImpl(); + add>(dataSource: IDataSource) { + this.#impl.add(dataSource); + return this as unknown as IBuilder>; + } + + addObject>(obj: NewT | (() => Promise)) { + return this.add(new ObjectDataSource(obj)); + } + + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, prefixOrPredicate?: string | Predicate) { + return this.add, unknown>>(new DictionaryDataSource(dictionary, hierarchySeparator ?? ':', prefixOrPredicate)); + } + + addEnvironment, TPrefix extends string = 'OPT_'>(env: TDic | (() => Promise), prefix: string = 'OPT_') { + // @ts-expect-error InflateDictionary's resulting type, for some reason, always asserts true against "unknown". TS bug? + return this.add>(new EnvironmentDataSource(env, prefix)); + } + + addFetched>(input: URL | RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, procesFn?: ProcessFetchResponse) { + return this.add(new FetchedDataSource(input, required, init, procesFn)); + } + + addJson>(json: string | (() => Promise), jsonParser?: JSON, reviver?: (this: any, key: string, value: any) => any) { + return this.add(new JsonDataSource(json, jsonParser, reviver)); + } + + addSingleValue(path: TKey | (() => Promise<[TKey, TValue]>), valueOrHierarchySeparator?: TValue | TSep, hierarchySeparator?: TSep) { + return this.add>(new SingleValueDataSource>(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); + } + + name(name: string) { + this.#impl.name(name); + return this; + } + + when(predicate: () => boolean, dataSourceName?: string) { + this.#impl.when(predicate, dataSourceName); + return this; + } + + includeEnvironment( + valueOrEnv: TEnvironments | IEnvironment, + propNameOrEnvNames?: TEnvironments[] | TEnvironmentKey, + propertyName?: TEnvironmentKey + ) { + this.#impl._lastCallWasDsAdd = false; + const propName = (typeof propNameOrEnvNames === 'string' ? propNameOrEnvNames : propertyName) ?? 'environment'; + const envNames = (propNameOrEnvNames && typeof propNameOrEnvNames !== 'string') ? propNameOrEnvNames : undefined; + let env: IEnvironment; + if (typeof valueOrEnv === 'object') { + env = valueOrEnv; + } + else { + env = buildEnvironment(valueOrEnv, envNames); + } + const envSource: IEnvironmentSource = { + name: propName, + environment: env + }; + return new EnvAwareBuilder & IncludeEnvironment>(envSource, this.#impl); + } + + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { + this.#impl.createUrlFunctions(wsPropertyNames, routeValuesRegExp); + return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; + } + + build(traceValueSources: boolean = false) { + return this.#impl.build(traceValueSources, p => p()) as unknown as Promise; + } +}; diff --git a/src/builders/BuilderImpl.ts b/src/builders/BuilderImpl.ts new file mode 100644 index 0000000..90f050c --- /dev/null +++ b/src/builders/BuilderImpl.ts @@ -0,0 +1,127 @@ +import { isConfigNode } from "../helpers.js"; +import makeWsUrlFunctions from "../makeWsUrlFunctions.js"; +import merge from "../Merge.js"; +import { IDataSource, Predicate } from "../wj-config.js"; + +interface IUrlData { + wsPropertyNames: string[]; + routeValuesRegExp: RegExp; +} + +interface IDataSourceDef { + dataSource: IDataSource>, + predicate?: (env?: any) => boolean; +} + +export class BuilderImpl { + /** + * Collection of data sources added to the builder. + */ + _dsDefs: IDataSourceDef[] = []; + /** + * URL data used to create URL functions out of specific property values in the resulting configuration object. + */ + _urlData?: IUrlData; + /** + * Flag to determine if the last call in the builder was the addition of a data source. + */ + _lastCallWasDsAdd: boolean = false; + + add(dataSource: IDataSource>) { + this._dsDefs.push({ + dataSource: dataSource + }); + dataSource.index = this._dsDefs.length - 1; + this._lastCallWasDsAdd = true; + } + name(name: string) { + 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: Predicate, dataSourceName?: string) { + 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: string | number | symbol | (string | number | symbol)[], routeValuesRegExp?: RegExp) { + this._lastCallWasDsAdd = false; + let propNames: (string | number | symbol)[]; + 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! as string[], + routeValuesRegExp: routeValuesRegExp ?? /\{(\w+)\}/g + }; + } + + async build(traceValueSources: boolean = false, evaluatePredicate: Predicate) { + this._lastCallWasDsAdd = false; + const qualifyingDs: IDataSource>[] = []; + let wjConfig: Record; + 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: Promise>[] = []; + qualifyingDs.forEach(ds => { + dsTasks.push(ds.getObject()); + }); + const sources = await Promise.all(dsTasks); + wjConfig = merge(sources, traceValueSources ? qualifyingDs : undefined); + } + else { + wjConfig = {}; + } + } + else { + wjConfig = {}; + } + const urlData = this._urlData; + if (urlData) { + urlData.wsPropertyNames.forEach((value) => { + const obj = wjConfig[value]; + if (isConfigNode(obj)) { + makeWsUrlFunctions(obj, 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 = []; + } + } + return wjConfig; + } +} diff --git a/src/builders/EnvAwareBuilder.ts b/src/builders/EnvAwareBuilder.ts new file mode 100644 index 0000000..3de64fb --- /dev/null +++ b/src/builders/EnvAwareBuilder.ts @@ -0,0 +1,153 @@ +import { DictionaryDataSource } from "../dataSources/DictionaryDataSource.js"; +import { EnvironmentDataSource } from "../dataSources/EnvironmentDataSource.js"; +import { FetchedDataSource } from "../dataSources/FetchedDataSource.js"; +import { JsonDataSource } from "../dataSources/JsonDataSource.js"; +import { ObjectDataSource } from "../dataSources/ObjectDataSource.js"; +import { SingleValueDataSource } from "../dataSources/SingleValueDataSource.js"; +import type { ConfigurationValue, IDataSource, IEnvAwareBuilder, IEnvironment, InflateDictionary, InflateKey, MergeResult, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "../wj-config.js"; +import { BuilderImpl } from "./BuilderImpl.js"; + +export interface IEnvironmentSource { + name?: string, + environment: IEnvironment; +} + +export class EnvAwareBuilder = {}> implements IEnvAwareBuilder { + /** + * Environment source. + */ + private _envSource: IEnvironmentSource; + #impl: BuilderImpl; + + constructor(envSource: IEnvironmentSource, impl: BuilderImpl) { + this._envSource = envSource; + this.#impl = impl; + } + + add>(dataSource: IDataSource) { + this.#impl.add(dataSource); + return this as unknown as IEnvAwareBuilder>; + } + + addObject>(obj: NewT | (() => Promise)) { + return this.add(new ObjectDataSource(obj)); + } + + addDictionary, TSep extends string = ':'>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + // @ts-expect-error + return this.add>(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + } + + addEnvironment, TPrefix extends string = 'OPT_'>(env: Record | (() => Promise>), prefix: string = 'OPT_') { + return this.add>>(new EnvironmentDataSource(env, prefix)); + } + + addFetched>(input: URL | RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, procesFn?: ProcessFetchResponse) { + return this.add(new FetchedDataSource(input, required, init, procesFn)); + } + + addJson>(json: string | (() => Promise), jsonParser?: JSON, reviver?: (this: any, key: string, value: any) => any) { + return this.add(new JsonDataSource(json, jsonParser, reviver)); + } + + addSingleValue(path: TKey | (() => Promise<[TKey, TValue]>), valueOrHierarchySeparator?: TValue | TSep, hierarchySeparator?: TSep) { + return this.add(new SingleValueDataSource>>(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); + } + + name(name: string) { + this.#impl.name(name); + return this; + } + + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { + this.#impl.createUrlFunctions(wsPropertyNames, routeValuesRegExp); + return this as unknown as IEnvAwareBuilder & UrlBuilderSectionWithCheck>; + } + + /** + * Boolean flag used to raise an error if there was no call to includeEnvironment() when it is known to be needed. + */ + private _envIsRequired: boolean = false; + + /** + * Dictionary of environment names that have been configured with a data source using the addPerEnvironment() + * helper function. The value is the number of times the environment name has been used. + */ + private _perEnvDsCount: Record, number> | null = null; + + addPerEnvironment>(addDs: (builder: IEnvAwareBuilder, envName: TEnvironments) => boolean | string) { + if (!this._envSource) { + throw new Error('Using addPerEnvironment() requires a prior call to includeEnvironment().'); + } + this._envSource.environment.all.forEach(n => { + const result = addDs(this, n); + if (result !== false) { + this.forEnvironment(n, typeof result === 'string' ? result : undefined); + } + }); + return this as unknown as IEnvAwareBuilder>; + } + + when(predicate: Predicate | undefined>, dataSourceName?: string) { + this.#impl.when(predicate, dataSourceName); + return this; + } + + forEnvironment(envName: Exclude, dataSourceName?: string) { + this._envIsRequired = true; + this._perEnvDsCount = this._perEnvDsCount ?? {} as Record, number>; + let count = this._perEnvDsCount[envName] ?? 0; + this._perEnvDsCount[envName] = ++count; + dataSourceName = + dataSourceName ?? + (count === 1 ? `${envName} (environment-specific)` : `${envName} #${count} (environment-specific)`); + return this.when(e => e?.current.name === envName, dataSourceName); + } + + whenAllTraits(traits: Traits, dataSourceName?: string) { + this._envIsRequired = true; + return this.when(env => { + return env!.hasTraits(traits) ?? false; + }, dataSourceName); + } + + whenAnyTrait(traits: Traits, dataSourceName?: string) { + this._envIsRequired = true; + return this.when(env => { + return env!.hasAnyTrait(traits); + }, dataSourceName); + } + + async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { + this.#impl._lastCallWasDsAdd = false; + // See if environment is required. + if (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.'); + } + // See if forEnvironment was used. + if (this._perEnvDsCount) { + // Ensure all specified environments are part of the possible list of environments. + let envCount = 0; + for (const e in this._perEnvDsCount) { + if (!this._envSource!.environment.all.includes(e as Exclude)) { + throw new Error(`The environment name "${e}" was used in a call to forEnvironment(), but said name is not part of the list of possible environment names.`); + } + ++envCount; + } + if (enforcePerEnvironmentCoverage) { + // Ensure all possible environment names were included. + const totalEnvs = (this._envSource as IEnvironmentSource).environment.all.length; + if (envCount !== totalEnvs) { + throw new Error(`Only ${envCount} environment(s) were configured using forEnvironment() out of a total of ${totalEnvs} environment(s). Either complete the list or disable this check when calling build().`); + } + } + } + const result = await this.#impl.build(traceValueSources, p => p(this._envSource?.environment)); + const envPropertyName = this._envSource.name ?? 'environment'; + if (result[envPropertyName] !== undefined) { + throw new Error(`Cannot use property name "${envPropertyName}" for the environment object because it was defined for something else.`); + } + result[envPropertyName] = this._envSource.environment; + return result as T; + } +} \ No newline at end of file diff --git a/src/DataSource.ts b/src/dataSources/DataSource.ts similarity index 85% rename from src/DataSource.ts rename to src/dataSources/DataSource.ts index e2b1699..25531c6 100644 --- a/src/DataSource.ts +++ b/src/dataSources/DataSource.ts @@ -1,4 +1,4 @@ -import type { IDataSourceInfo } from "wj-config"; +import type { IDataSourceInfo } from "../wj-config.js"; export class DataSource { name: string; diff --git a/src/dataSources/DictionaryDataSource.ts b/src/dataSources/DictionaryDataSource.ts new file mode 100644 index 0000000..9755c00 --- /dev/null +++ b/src/dataSources/DictionaryDataSource.ts @@ -0,0 +1,128 @@ +import { attemptParse, forEachProperty, isConfigNode } from "../helpers.js"; +import type { ConfigurationNode, ConfigurationValue, Dictionary, IDataSource, Predicate } from "../wj-config.js"; +import { DataSource } from "./DataSource.js"; + +const processKey = (key: string, hierarchySeparator: string, prefix?: string) => { + if (prefix) { + key = key.substring(prefix.length); + } + return key.split(hierarchySeparator); +}; + +const ensurePropertyValue = (obj: ConfigurationNode, name: string) => { + if (obj[name] === undefined) { + obj[name] = {}; + } + return obj[name]; +} + +export class DictionaryDataSource> extends DataSource implements IDataSource { + #dictionary: Record | (() => Promise>); + #hierarchySeparator: string; + #prefixOrPredicate?: string | Predicate; + + #buildPredicate(): [Predicate, string] { + let predicateFn: Predicate = _ => true; + let prefix: string = ''; + if (this.#prefixOrPredicate) { + if (typeof this.#prefixOrPredicate === "string") { + prefix = this.#prefixOrPredicate; + predicateFn = name => name.startsWith(prefix); + } + else { + predicateFn = this.#prefixOrPredicate; + } + } + return [predicateFn, prefix]; + } + + #validateDictionary(dic: unknown) { + if (!isConfigNode(dic)) { + throw new Error('The provided dictionary must be a flat object.'); + } + const [predicateFn, prefix] = this.#buildPredicate(); + forEachProperty(dic, (k, v) => { + if (!predicateFn(k)) { + // This property does not qualify, so skip its validation. + return false; + } + if (isConfigNode(v)) { + throw new Error(`The provided dictionary must be a flat object: Property ${k} has a non-scalar value.`); + } + }); + } + + #inflateDictionary(dic: Dictionary) { + const result = {} as T; + if (!dic) { + return result; + } + const [predicateFn, prefix] = this.#buildPredicate(); + forEachProperty(dic, (key, value) => { + if (predicateFn(key)) { + // Object values are disallowed because a dictionary's source is assumed to be flat. + // if (isConfigNode(value)) { + // throw new Error(`Dictionary data sources cannot hold object values. Key: ${key}`); + // } + const keyParts = processKey(key, this.#hierarchySeparator, prefix); + let obj: ConfigurationValue | ConfigurationNode = result; + let keyPath = ''; + for (let i = 0; i < keyParts.length - 1; ++i) { + keyPath += (keyPath.length ? '.' : '') + keyParts[i]; + obj = ensurePropertyValue(obj, keyParts[i]); + if (!isConfigNode(obj)) { + throw new Error(`Cannot set the value of property "${key}" because "${keyPath}" has already been created as a leaf value.`); + } + } + // Ensure there is no value override. + if (obj[keyParts[keyParts.length - 1]]) { + throw new Error(`Cannot set the value of variable "${key}" because "${keyParts[keyParts.length - 1]}" has already been created as an object to hold other values.`); + } + // If the value is a string, attempt parsing. This is to support data sources that can only hold strings + // as values, such as enumerating actual system environment variables. + if (typeof value === 'string') { + value = attemptParse(value); + } + obj[keyParts[keyParts.length - 1]] = value; + } + }); + return result; + } + + constructor(dictionary: Dictionary | (() => Promise), hierarchySeparator: string, prefixOrPredicate?: string | Predicate) { + super('Dictionary'); + if (!hierarchySeparator) { + throw new Error('Dictionaries must specify a hierarchy separator.'); + } + if (typeof hierarchySeparator !== 'string') { + throw new Error('The hierarchy separator must be a string.'); + } + this.#hierarchySeparator = hierarchySeparator; + if (prefixOrPredicate !== undefined) { + if (typeof prefixOrPredicate === 'string' && prefixOrPredicate.length === 0) { + throw new Error('The provided prefix value cannot be an empty string.'); + } + if (typeof prefixOrPredicate !== 'string' && typeof prefixOrPredicate !== 'function') { + throw new Error('The prefix argument can only be a string or a function.'); + } + if (typeof prefixOrPredicate === 'string' && prefixOrPredicate.length === 0) { + throw new Error('An empty string cannot be used as prefix.'); + } + } + this.#prefixOrPredicate = prefixOrPredicate; + if (typeof dictionary !== 'function') { + this.#validateDictionary(dictionary); + } + this.#dictionary = dictionary; + } + + async getObject(): Promise { + let dic = this.#dictionary; + if (dic && typeof dic === 'function') { + dic = await dic(); + this.#validateDictionary(dic); + } + const inflatedObject = this.#inflateDictionary(dic); + return Promise.resolve(inflatedObject); + } +} diff --git a/src/dataSources/EnvironmentDataSource.ts b/src/dataSources/EnvironmentDataSource.ts new file mode 100644 index 0000000..1725735 --- /dev/null +++ b/src/dataSources/EnvironmentDataSource.ts @@ -0,0 +1,12 @@ +import type { Dictionary } from "../wj-config.js"; +import { DictionaryDataSource } from "./DictionaryDataSource.js"; + +export class EnvironmentDataSource> extends DictionaryDataSource { + constructor(env: Dictionary | (() => Promise), prefix?: string) { + super(env, '__', prefix); + if (!prefix) { + throw new Error('The prefix is mandatory to avoid accidental imports of sensitive data from environment variable values.'); + } + this.name = 'Environment'; + } +} diff --git a/src/FetchedDataSource.ts b/src/dataSources/FetchedDataSource.ts similarity index 89% rename from src/FetchedDataSource.ts rename to src/dataSources/FetchedDataSource.ts index cb35121..712d6b0 100644 --- a/src/FetchedDataSource.ts +++ b/src/dataSources/FetchedDataSource.ts @@ -1,12 +1,12 @@ -import type { ICoreConfig, ProcessFetchResponse } from "wj-config"; +import type { ProcessFetchResponse } from "../wj-config.js"; import { DataSource } from "./DataSource.js"; -export default class FetchedDataSource extends DataSource { +export class FetchedDataSource> extends DataSource { private _input: URL | RequestInfo | (() => Promise); private _required: boolean; private _init?: RequestInit; - private _processFn: ProcessFetchResponse; - constructor(input: URL | RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, processFn?: ProcessFetchResponse) { + private _processFn: ProcessFetchResponse; + constructor(input: URL | RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, processFn?: ProcessFetchResponse) { super(typeof input === 'string' ? `Fetch ${input}` : 'Fetched Configuration'); this._input = input; this._required = required; @@ -28,12 +28,12 @@ export default class FetchedDataSource extends DataSource { }); } - async getObject(): Promise { + async getObject(): Promise { let input = this._input; if (typeof input === 'function') { input = await input(); } - let data: ICoreConfig = {}; + let data = {} as T; try { const response = await fetch(input, this._init); try { diff --git a/src/JsonDataSource.ts b/src/dataSources/JsonDataSource.ts similarity index 81% rename from src/JsonDataSource.ts rename to src/dataSources/JsonDataSource.ts index 3bbef44..49b8960 100644 --- a/src/JsonDataSource.ts +++ b/src/dataSources/JsonDataSource.ts @@ -1,6 +1,6 @@ import { DataSource } from "./DataSource.js"; -export default class JsonDataSource extends DataSource { +export class JsonDataSource> extends DataSource { private _json: string | (() => Promise); private _jsonParser: JSON; private _reviver?: (this: any, key: string, value: any) => any; @@ -16,6 +16,6 @@ export default class JsonDataSource extends DataSource { if (typeof json === 'function') { json = await json(); } - return this._jsonParser.parse(json, this._reviver); + return this._jsonParser.parse(json, this._reviver) as T; } } diff --git a/src/ObjectDataSource.ts b/src/dataSources/ObjectDataSource.ts similarity index 65% rename from src/ObjectDataSource.ts rename to src/dataSources/ObjectDataSource.ts index 5ab9b4f..499330e 100644 --- a/src/ObjectDataSource.ts +++ b/src/dataSources/ObjectDataSource.ts @@ -1,18 +1,18 @@ -import type { ICoreConfig, IDataSource } from "wj-config"; +import { isConfigNode } from "../helpers.js"; +import type { IDataSource } from "../wj-config.js"; import { DataSource } from "./DataSource.js"; -import { isConfig } from "./helpers.js"; /** * Configuration data source class that injects a pre-build JavaScript object into the configuration build chain. */ -export class ObjectDataSource extends DataSource implements IDataSource { +export class ObjectDataSource> extends DataSource implements IDataSource { /** * The object to inject. */ - private _obj: ICoreConfig | (() => Promise); + private _obj: T | (() => Promise); - #validateObject(obj: ICoreConfig) { - if (!isConfig(obj)) { + #validateObject(obj: T) { + if (!isConfigNode(obj)) { throw new Error('The provided object is not suitable as configuration data source.'); } } @@ -21,7 +21,7 @@ export class ObjectDataSource extends DataSource implements IDataSource { * Initializes a new instance of this class. * @param obj Data object to inject into the configuration build chain. */ - constructor(obj: ICoreConfig | (() => Promise)) { + constructor(obj: T | (() => Promise)) { super('Object'); if (typeof obj !== 'function') { this.#validateObject(obj); @@ -29,7 +29,7 @@ export class ObjectDataSource extends DataSource implements IDataSource { this._obj = obj; } - async getObject(): Promise { + async getObject(): Promise { let obj = this._obj; if (typeof obj === 'function') { obj = await obj(); diff --git a/src/SingleValueDataSource.ts b/src/dataSources/SingleValueDataSource.ts similarity index 54% rename from src/SingleValueDataSource.ts rename to src/dataSources/SingleValueDataSource.ts index 89ca2e3..6dab2e3 100644 --- a/src/SingleValueDataSource.ts +++ b/src/dataSources/SingleValueDataSource.ts @@ -1,12 +1,12 @@ -import type { ConfigurationValue, ICoreConfig } from "wj-config"; -import DictionaryDataSource from "./DictionaryDataSource.js"; +import type { ConfigurationValue, Dictionary } from "../wj-config.js"; +import { DictionaryDataSource } from "./DictionaryDataSource.js"; -const buildDictionary = (key: string | (() => Promise<[string, ConfigurationValue]>), value?: ConfigurationValue): ICoreConfig | (() => Promise) => { +function buildDictionary(key: string | (() => Promise<[string, ConfigurationValue]>), value?: ConfigurationValue) { if (!key) { throw new Error('No valid path was provided.'); } const dicFn = (k: string, v: ConfigurationValue) => { - const dic: ICoreConfig = {}; + const dic: Dictionary = {}; dic[k] = v; return dic; }; @@ -19,8 +19,12 @@ const buildDictionary = (key: string | (() => Promise<[string, ConfigurationValu return dicFn(key, value); } -export default class SingleValueDataSource extends DictionaryDataSource { - constructor(path: string | (() => Promise<[string, ConfigurationValue]>), value?: ConfigurationValue, hierarchySeparator: string = ':') { +export class SingleValueDataSource> extends DictionaryDataSource { + constructor( + path: string | (() => Promise<[string, ConfigurationValue]>), + value?: ConfigurationValue, + hierarchySeparator: string = ':' + ) { super(buildDictionary(path, value), hierarchySeparator); if (typeof path === 'string') { this.name = `Single Value: ${path}`; diff --git a/src/dataSources/index.ts b/src/dataSources/index.ts new file mode 100644 index 0000000..1e8b5d9 --- /dev/null +++ b/src/dataSources/index.ts @@ -0,0 +1,8 @@ +export { DataSource } from "./DataSource.js"; +export { DictionaryDataSource } from "./DictionaryDataSource.js"; +export { EnvironmentDataSource } from "./EnvironmentDataSource.js"; +export { FetchedDataSource } from "./FetchedDataSource.js"; +export { JsonDataSource } from "./JsonDataSource.js"; +export { ObjectDataSource } from "./ObjectDataSource.js"; +export { SingleValueDataSource } from "./SingleValueDataSource.js"; + diff --git a/src/helpers.ts b/src/helpers.ts index 04ccbe0..baaee7e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,6 +1,6 @@ 'use strict'; -import { IConfig } from "wj-config"; +import { ConfigurationNode, Dictionary } from "./wj-config.js"; /** * Tests the provided object to determine if it is an array. @@ -15,8 +15,34 @@ export function isArray(obj: unknown): obj is any[] { return Array.isArray(obj); * @param obj Object to test. * @returns True if the object is a non-leaf object; false otherwise. */ -export function isConfig(obj: unknown): obj is IConfig { - return typeof obj === 'object' && !isArray(obj) && !(obj instanceof Date); +export function isConfigNode(obj: unknown): obj is ConfigurationNode { + return typeof obj === 'object' + && obj !== null + && !isArray(obj) + && !(obj instanceof Date) + && !(obj instanceof Set) + && !(obj instanceof Map) + && !(obj instanceof WeakMap) + && !(obj instanceof WeakRef) + && !(obj instanceof WeakSet) + ; +} + +/** + * Tests a particular object to determine if it is a dictionary. + * @param obj Object to test. + * @returns `true` if the object is a dictionary, or `false` otherwise. + */ +export function isDictionary(obj: unknown): obj is Dictionary { + if (typeof obj === 'object' && obj !== null && !isArray(obj) && !(obj instanceof Date)) { + for (let key in obj) { + if (isConfigNode((obj as Record)[key])) { + return false; + } + } + return true; + } + return false; } /** @@ -79,7 +105,7 @@ export const attemptParse = (value: (string | undefined | null)) => { else if (isFloat.test(value)) { parsedValue = Number.parseFloat(value); } - if (parsedValue !== NaN && parsedValue !== null) { + if (parsedValue !== null && !isNaN(parsedValue)) { return parsedValue; } // Return as string. diff --git a/src/index.ts b/src/index.ts index 9c84e5a..51b040a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ -import { IBuilder } from "wj-config"; -import Builder from "./Builder.js"; +import { Builder } from "./builders/Builder.js"; +import { type IBuilder } from "./wj-config.js"; -export * from "./Environment.js"; +export * from "./buildEnvironment.js"; export * from "./EnvironmentDefinition.js"; -export * from "./DataSource.js"; +export type * from "./wj-config.js"; export default function wjConfig(): IBuilder { return new Builder(); } diff --git a/src/makeWsUrlFunctions.ts b/src/makeWsUrlFunctions.ts index 93545b2..0c66815 100644 --- a/src/makeWsUrlFunctions.ts +++ b/src/makeWsUrlFunctions.ts @@ -1,5 +1,5 @@ -import type { ICoreConfig, IWsParent, IWsPath, QueryString, RouteValues, RouteValuesFunction } from "wj-config"; -import { forEachProperty, isArray, isFunction, isConfig } from "./helpers.js"; +import { forEachProperty, isArray, isConfigNode } from "./helpers.js"; +import type { ConfigurationNode, QueryStringArg, RouteReplacementArg, RouteValuesFn, UrlNode, UrlRoot } from "./wj-config.js"; const noop = (x?: any) => ''; @@ -8,17 +8,17 @@ const rootUrlObjectProps = ['host', 'rootPath']; const rootUrlObjectPropsForBrowser = ['host', 'rootPath', 'scheme', 'port']; function buildUrlImpl( - this: IWsPath, + this: UrlNode, path: string, - routeValues?: RouteValues, + routeValues?: RouteReplacementArg, routeRegex?: RegExp, - queryString?: QueryString + queryString?: QueryStringArg ) { - let routeValuesFn: RouteValuesFunction | undefined = undefined; + let routeValuesFn: RouteValuesFn | undefined = undefined; let index = 0; if (routeValues) { - if (isFunction(routeValues)) { - routeValuesFn = routeValues; + if (typeof routeValues === 'function') { + routeValuesFn = routeValues as RouteValuesFn; } else if (isArray(routeValues)) { routeValuesFn = n => routeValues[index++]; @@ -41,7 +41,7 @@ function buildUrlImpl( } else if (url.length - qmPos - 1) { url += '&' } - let qsValue: string | ICoreConfig | undefined; + let qsValue: string | Record | undefined; if (typeof queryString === 'function') { qsValue = queryString(); } @@ -65,7 +65,14 @@ function buildUrlImpl( return url; } -function parentRootPath(this: IWsParent, isBrowser: boolean) { +/** + * Builds a relative, absolute or full URL using the information found in the root node and taking into account if the + * code is running in the browser. + * @param this URL root node holding the URL information. + * @param isBrowser A Boolean value that indicates if code is running in the browser. + * @returns The relative, absolute or full URL, product of the information in the root node. + */ +function parentRootPath(this: UrlRoot, isBrowser: boolean) { if ((!this.host && !isBrowser) || (!this.host && !this.port && !this.scheme)) { // When no host outside the browser, or no host, port or scheme in the browser, // build a relative URL starting with the root path. @@ -74,16 +81,22 @@ function parentRootPath(this: IWsParent, isBrowser: boolean) { return `${(this.scheme ?? 'http')}://${(this.host ?? globalThis.window?.location?.hostname ?? '')}${(this.port ? `:${this.port}` : '')}${(this.rootPath ?? '')}`; } -function pathRootPath(this: IWsPath, parent: IWsPath) { +/** + * Builds a relative, absolute or full URL by appending path information to the generated URL from the parent node. + * @param this URL node holding path information. + * @param parent Parent node. + * @returns The relative, absolute or full URL built by appending path information to the parent's generated URL. + */ +function pathRootPath(this: UrlNode, parent: UrlNode) { const rp = (parent[rootPathFn] ?? noop)(); return `${rp}${(this.rootPath ?? '')}`; } -function makeWsUrlFunctions(ws: IWsParent | ICoreConfig, routeValuesRegExp: RegExp, isBrowser: boolean, parent?: IWsParent) { +function makeWsUrlFunctions(ws: UrlRoot | ConfigurationNode, routeValuesRegExp: RegExp, isBrowser: boolean, parent?: UrlRoot) { if (!ws) { return; } - if (!isConfig(ws)) { + if (!isConfigNode(ws)) { throw new Error(`Cannot operate on a non-object value (provided value is of type ${typeof ws}).`); } const shouldConvert = (name: string) => { @@ -97,46 +110,46 @@ function makeWsUrlFunctions(ws: IWsParent | ICoreConfig, routeValuesRegExp: RegE ]; return !name.startsWith('_') && !exceptions.includes(name); }; - const isRoot = (obj: object) => { + const isUrlRoot = (obj: object): obj is UrlRoot => { // An object is a root object if it has host or rootPath, or if code is running in a browser, an object is a // root object if it has any of the reserved properties. let yes = false; forEachProperty(obj, k => yes = rootUrlObjectProps.includes(k) || (isBrowser && rootUrlObjectPropsForBrowser.includes(k))); return yes; }; + const isUrlNode = (obj: unknown, parent: UrlNode | undefined): obj is UrlNode => { + return !!parent?.[rootPathFn] || !!(obj as UrlNode)._rootPath; + } // Add the _rootPath() function. - let canBuildUrl = true; - if (isRoot(ws) && (!parent?.buildUrl)) { + if (isUrlRoot(ws) && (!parent?.buildUrl)) { ws[rootPathFn] = function () { - return parentRootPath.bind((ws as IWsPath))(isBrowser); + return parentRootPath.bind(ws)(isBrowser); }; } - else if (parent !== undefined && parent[rootPathFn] !== undefined) { + else if (isUrlNode(ws, parent)) { ws[rootPathFn] = function () { - return pathRootPath.bind((ws as IWsPath))(parent); + return pathRootPath.bind(ws)(parent!); }; } - else { - canBuildUrl = false; - } - if (canBuildUrl) { + if (isUrlNode(ws, parent)) { // Add the buildUrl function. - ws.buildUrl = function (path: string, routeValues?: RouteValues, queryString?: QueryString) { - return buildUrlImpl.bind((ws as IWsPath))(path, routeValues, routeValuesRegExp, queryString); + ws.buildUrl = function (path: string, routeValues?: RouteReplacementArg, queryString?: QueryStringArg) { + return buildUrlImpl.bind((ws as UrlNode))(path, routeValues, routeValuesRegExp, queryString); }; } + const canServeAsParent = isUrlNode(ws, undefined); // For every non-object property in the object, make it a function. // Properties that have an object are recursively configured. forEachProperty(ws, (key, value) => { const sc = shouldConvert(key); - if (sc && canBuildUrl && typeof value === 'string') { - ws[key] = function (routeValues?: RouteValues, queryString?: QueryString) { - return ((ws as IWsPath).buildUrl ?? noop)(value, routeValues, queryString); + if (sc && canServeAsParent && typeof value === 'string') { + (ws as UrlNode)[key] = function (routeValues?: RouteReplacementArg, queryString?: QueryStringArg) { + return (ws.buildUrl ?? noop)(value, routeValues, queryString); }; } - else if (sc && isConfig(value)) { + else if (sc && isConfigNode(value)) { // Object value. - makeWsUrlFunctions(value as IWsParent, routeValuesRegExp, isBrowser, (ws as IWsParent)); + makeWsUrlFunctions(value, routeValuesRegExp, isBrowser, canServeAsParent ? ws : undefined); } }); }; diff --git a/src/package-lock.json b/src/package-lock.json index 50ac97f..2139094 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,7 +1,7 @@ { "name": "wj-config", "version": "2.0.2", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,29 +9,25 @@ "version": "2.0.2", "license": "MIT-open-group", "devDependencies": { - "typescript": "^4.7.4" + "typescript": "^5.6.2" + }, + "engines": { + "node": ">=16.9.0" } }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } } - }, - "dependencies": { - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - } } } diff --git a/src/package.json b/src/package.json index ee6cace..32a420e 100644 --- a/src/package.json +++ b/src/package.json @@ -1,10 +1,13 @@ { "name": "wj-config", - "version": "2.0.2", + "version": "3.0.0-beta.1", "main": "index.js", "type": "module", "description": "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 convention.", - "author": "José Pablo Ramírez Vargas ", + "author": { + "email": "webJose@gmail.com", + "name": "José Pablo Ramírez Vargas" + }, "keywords": [ "config", "wj", @@ -33,9 +36,21 @@ "license": "MIT-open-group", "homepage": "https://github.com/WJSoftware/wj-config#readme", "devDependencies": { - "typescript": "^4.7.4" + "typescript": "^5.6.2" }, "types": "wj-config.d.ts", + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.js", + "default": "./index.js" + }, + "./sources": { + "types": "./dataSources/index.d.ts", + "import": "./dataSources/index.js", + "default": "./dataSources/index.js" + } + }, "scripts": { "build": "npx tsc" }, diff --git a/src/wj-config.d.ts b/src/wj-config.d.ts index 790e00f..1dd8a7f 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -1,422 +1,554 @@ -declare module 'wj-config' { +/** + * Possible value types in properties of a configuration object. + */ +export type SingleConfigurationValue = string | number | Date | boolean | undefined | null; + +/** + * Defines the type of configuration leaf properties. + */ +export type ConfigurationValue = SingleConfigurationValue | SingleConfigurationValue[]; + +/** + * A configuration node. + */ +export interface ConfigurationNode { + [x: string]: ConfigurationValue | ConfigurationNode +} + +/** + * Types an object-merging operation's result. + */ +export type MergeResult, NewT> = (Omit & { + [K in keyof NewT]-?: K extends keyof T ? + ( + T[K] extends Record ? + (NewT[K] extends Record ? MergeResult : never) : + (NewT[K] extends Record ? never : NewT[K]) + ) : NewT[K] +}) extends infer R ? { [K in keyof R]: R[K] } : never; + +/** + * Types individual dictionary values and inflates them. + */ +export type InflateKey = TKey extends `${TPrefix}${infer FullKey}` ? + FullKey extends `${infer Key}${TSep}${infer Rest}` ? + { + [K in Key]: InflateKey + } : + { + [K in TKey]: TValue; + } : never; + +/** + * Inflates entire dictionaries into their corresponding final objects. + */ +export type InflateDictionary, TSep extends string, TPrefix extends string = ""> = { + [K in keyof TDic]: (x: InflateKey) => void +} extends Record void> ? I : never; + +/** + * Defines the shape of dictionaries. + */ +export type Dictionary = Record; + +/** + * WJ-Config module's entry point. Creates a builder object that is used to specify the various configuration + * sources and settings. + */ +export default function wjConfig(): IBuilder; + +/** + * Predicate function that evaluates an arbitrary number of criteria against the data and returns a judgment in terms + * of a Boolean value. + */ +export type Predicate = (data: T) => boolean; + +/** + * Defines functions that process a fetched response object and returns configuration data. + */ +export type ProcessFetchResponse> = (response: Response) => Promise; + +/** + * Defines the capabilities of a configurtion object that contains an environment object in the environment property. + */ +export type IncludeEnvironment = { + [K in Key as `${K}`]: IEnvironment; +} + +/** + * Defines the requirements of objects that wish to provide JSON-parsing services. + */ +export interface IJsonParser> { /** - * WJ-Config module's entry point. Creates a builder object that is used to specify the various configuration - * sources and settings. + * Parses the provided JSON string data and returns a JavaScript object. + * @param json The JSON string to parse. */ - export default function wjConfig(): IBuilder; + parse(json: string): T; +} +/** + * Defines the capabilities required from data source information objects used in value tracing. + */ +export interface IDataSourceInfo { /** - * Predicate function that evaluates an arbitrary number of criteria against the data and returns a judgment in terms - * of a Boolean value. + * Provides the name of the data source instance that can be used in messages and logs. */ - export type Predicate = (data: T) => boolean; + name: string; /** - * Possible values in a configuration object. + * Index (position) of the data source object in the builder's list of data sources. */ - export type ConfigurationValue = string | number | Date | boolean | IEnvironment | IDefaultEnvironment | undefined | null | Function | ICoreConfig | IDataSourceInfo | IDataSourceInfo[]; + index?: number; +} +/** + * Defines the capabilities required from data sources. + */ +export interface IDataSource = Record> extends IDataSourceInfo { /** - * Type alias that describes the data type of interim or final configuration objects. + * Asynchronously obtains the object that will be used as building block in the creation of the final + * configuration object. */ - export type IConfig = ICoreConfig | IEnvConfig | IDefaultEnvConfig; - - export type ProcessFetchResponse = (response: Response) => Promise; + getObject(): Promise; /** - * Defines the core and most basic capabilities found in configuration objects. + * Returns a data source information object on demand. This is used when building a configuration object with + * value tracing. */ - export interface ICoreConfig { - [x: string | symbol]: ConfigurationValue; - } + trace(): IDataSourceInfo; +} + +/** + * Defines the shape of configuration-tracing objects. + */ +export interface Trace { + [x: string]: IDataSourceInfo | Trace; +} +/** + * Defines the capabilities required from configuration builders. + */ +export interface IBuilder = {}> { /** - * Defines the capabilities of a configurtion object that contains an environment object in the environment property. + * Adds the provided data source to the collection of data sources that will be used to build the + * configuration object. + * @param dataSource The data source to include. */ - export interface IEnvConfig extends ICoreConfig { - environment: IEnvironment; - } - + add>(dataSource: IDataSource): IBuilder>; /** - * Defines the capabilities of a configurtion object that contains an environment object in the environment property - * created using the default list of environment names. + * Adds the specified object to the collection of data sources that will be used to build the configuration + * object. + * @param obj Data object to include as part of the final configuration data, or a function that returns said + * object. */ - export interface IDefaultEnvConfig extends ICoreConfig { - environment: IDefaultEnvironment - } - - export interface IJsonParser { - parse(json: string): ICoreConfig - } - + addObject>(obj: NewT | (() => Promise)): IBuilder>; /** - * Defines the capabilities required from data source information objects used in value tracing. + * Adds the specified dictionary to the collection of data sources that will be used to build the configuration + * object. + * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, + * or a function that returns said object. + * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. + * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the + * prefix is always removed after the dictionary is processed. If no prefix is provided, then all dictionary + * entries will contribute to the configuration data. */ - export interface IDataSourceInfo { - /** - * Provides the name of the data source instance that can be used in messages and logs. - */ - name: string; - - /** - * Index (position) of the data source object in the builder's list of data sources. - */ - index?: number; - } - + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, prefix?: string): IBuilder>>; /** - * Defines the capabilities required from data sources. + * Adds the specified dictionary to the collection of data sources that will be used to build the configuration + * object. + * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, + * or a function that returns said object. + * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. + * @param predicate Optional predicate function that is called for every property in the dictionary. Only when + * the return value of the predicate is true the property is included in configuration. */ - export interface IDataSource extends IDataSourceInfo { - /** - * Asynchronously obtains the object that will be used as building block in the creation of the final - * configuration object. - */ - getObject(): Promise; - - /** - * Returns a data source information object on demand. This is used when building a configuration object with - * value tracing. - */ - trace(): IDataSourceInfo; - } - + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, predicate?: Predicate): IBuilder>>; /** - * Defines the capabilities required from configuration builders. - */ - export interface IBuilder { - /** - * Adds the provided data source to the collection of data sources that will be used to build the - * configuration object. - * @param dataSource The data source to include. - */ - add(dataSource: IDataSource): IBuilder; - /** - * Adds the specified object to the collection of data sources that will be used to build the configuration - * object. - * @param obj Data object to include as part of the final configuration data, or a function that returns said - * object. - */ - addObject(obj: ICoreConfig | (() => Promise)): IBuilder; - - /** - * Adds the specified dictionary to the collection of data sources that will be used to build the configuration - * object. - * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, - * or a function that returns said object. - * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. - * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the - * prefix is always removed after the dictionary is processed. If no prefix is provided, then all dictionary - * entries will contribute to the configuration data. - */ - addDictionary(dictionary: ICoreConfig | (() => Promise), hierarchySeparator?: string, prefix?: string): IBuilder; - - /** - * Adds the specified dictionary to the collection of data sources that will be used to build the configuration - * object. - * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, - * or a function that returns said object. - * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. - * @param predicate Optional predicate function that is called for every property in the dictionary. Only when - * the return value of the predicate is true the property is included in configuration. - */ - addDictionary(dictionary: ICoreConfig | (() => Promise), hierarchySeparator?: string, predicate?: Predicate): IBuilder; - - /** - * Adds the qualifying environment variables to the collection of data sources that will be used to build the - * configuration object. - * @param env Environment object containing the environment variables to include in the configuration, or a - * function that returns said object. - * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the - * prefix is always removed after processing. To avoid exposing non-application data as configuration, a prefix - * is always used. If none is specified, the default prefix is OPT_. - */ - addEnvironment(env: ICoreConfig | (() => Promise), prefix?: string): IBuilder; - - /** - * Adds a fetch operation to the collection of data sources that will be used to build the configuration - * object. - * @param url URL to fetch. - * @param required Optional Boolean value indicating if the fetch must produce an object. - * @param init Optional fetch init data. Refer to the fecth() documentation for information. - * @param processFn Optional processing function that must return the configuration data as an object. - */ - addFetched(url: URL | (() => Promise), required: boolean = true, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder; - - /** - * Adds a fetch operation to the collection of data sources that will be used to build the configuration - * object. - * @param request Request object to use when fetching. Refer to the fetch() documentation for information. - * @param required Optional Boolean value indicating if the fetch must produce an object. - * @param init Optional fetch init data. Refer to the fecth() documentation for information. - * @param processFn Optional processing function that must return the configuration data as an object. - */ - addFetched(request: RequestInfo | (() => Promise), required: boolean = true, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder; - - /** - * Adds the specified JSON string to the collection of data sources that will be used to build the - * configuration object. - * @param json The JSON string to parse into a JavaScript object, or a function that returns said string. - * @param jsonParser Optional JSON parser. If not specified, the built-in JSON object will be used. - * @param reviver Optional reviver function. For more information see the JSON.parse() documentation. - */ - addJson(json: string | (() => Promise), jsonParser?: JSON, reviver?: (this: any, key: string, value: any) => any): IBuilder; - - /** - * Adds a single value to the collection of data sources that will be used to build the configuration object. - * @param path Key comprised of names that determine the hierarchy of the value. - * @param value Value of the property. - * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. - */ - addSingleValue(path: string, value?: ConfigurationValue, hierarchySeparator: string = ':'): IBuilder; - - /** - * Adds a single value to the collection of data sources that will be used to build the configuration object. - * @param dataFn Function that returns the [key, value] tuple that needs to be added. - * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. - */ - addSingleValue(dataFn: () => Promise<[string, ConfigurationValue]>, hierarchySeparator: string = ':'): IBuilder; - - /** - * Special function that allows the developer the opportunity to add one data source per defined environment. - * - * The function iterates through all possible environments and calls the addDs function for each one. It is - * assumed that the addDs function will add zero or one data source. To signal no data source was added, - * addDs must return the boolean value "false". - * @param addDs Function that is meant to add a single data source of any type that is associated to the - * provided environment name. - */ - addPerEnvironment(addDs: (builder: IBuilder, envName: string) => boolean | string): IBuilder; - - /** - * Sets the data source name of the last data source added to the builder. - * @param name Name for the data source. - */ - name(name: string): IBuilder; - - /** - * Makes the last-added data source conditionally inclusive. - * @param predicate Predicate function that is run whenever the build function runs. If the predicate returns - * true, then the data source will be included; if it returns false, then the data source is skipped. - * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a - * shortcut to include a call to the name() function. Equivalent to when().name(). - */ - when(predicate: Predicate, dataSourceName?: string): IBuilder; - - /** - * Makes the last-added data source conditionally included only if the current environment possesses all of - * the listed traits. - * @param traits The traits the current environment must have for the data source to be added. - * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a - * shortcut to include a call to the name() function. Equivalent to whenAllTraits().name(). - */ - whenAllTraits(traits: Traits, dataSourceName?: string): IBuilder; - - /** - * Makes the last-added data source conditionally included if the current environment possesses any of the - * listed traits. Only one coincidence is necessary. - * @param traits The list of possible traits the current environment may have in order for the data source to - * be included. - * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a - * shortcut to include a call to the name() function. Equivalent to whenAnyTrait().name(). - */ - whenAnyTrait(traits: Traits, dataSourceName?: string): IBuilder; - - /** - * Makes the last-added data source conditionally included if the current environment's name is equal to the - * provided environment name. - * @param envName The environment name to use to conditionally include the last-added data source. - * @param dataSourceName Optional data source name. Provided to simplify the build chain and is - * merely a shortcut to include a call to the name() function. - */ - forEnvironment(envName: string, dataSourceName?: string): IBuilder; - - /** - * Adds the provided environment object as a property of the final configuration object. - * @param env Previously created environment object. - * @param propertyName Optional property name for the environment object. - */ - includeEnvironment(env: IEnvironment, propertyName?: string): IBuilder; - - /** - * Adds an environment object created with the provided value and names as a property of the final configuration - * object. - * @param value Current environment name. - * @param envNames List of possible environment names. - * @param propertyName Optional property name for the environment object. - */ - includeEnvironment(value: string, envNames?: string[], propertyName?: string): IBuilder; - - /** - * Creates URL functions in the final configuration object for URL's defined according to the wj-config standard. - * @param wsPropertyNames Optional list of property names whose values are expected to be objects that contain - * host, port, scheme or root path data at some point in their child hierarchy. If not provided, then the default - * list will be used. It can also be a single string, which is the same as a 1-element array. - * @param routeValueRegExp Optional regular expression used to identify replaceable route values. If this is not - * provided, then the default regular expression will match route values of the form {}, such as - * {code} or {id}. - */ - createUrlFunctions(wsPropertyNames?: string | string[], routeValueRegExp?: RegExp): IBuilder; - - /** - * Asynchronously builds the final configuration object. - */ - build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true): Promise; - } - + * Adds the qualifying environment variables to the collection of data sources that will be used to build the + * configuration object. + * @param env Environment object containing the environment variables to include in the configuration, or a + * function that returns said object. + * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the + * prefix is always removed after processing. To avoid exposing non-application data as configuration, a prefix + * is always used. If none is specified, the default prefix is OPT_. + */ + addEnvironment, TPrefix extends string = 'OPT_'>(env: TDic | (() => Promise), prefix?: TPrefix): IBuilder>>; /** - * Function type that allows environment objects to declare testing functions, such as isProduction(). + * Adds a fetch operation to the collection of data sources that will be used to build the configuration object. + * @param url URL to fetch. + * @param required Optional Boolean value indicating if the fetch must produce an object. + * @param init Optional fetch init data. Refer to the fecth() documentation for information. + * @param processFn Optional processing function that must return the configuration data as an object. */ - export type EnvironmentTest = () => boolean; - + addFetched>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder>; /** - * Defines the capabilities required from environment objects. + * Adds a fetch operation to the collection of data sources that will be used to build the configuration + * object. + * @param request Request object to use when fetching. Refer to the fetch() documentation for information. + * @param required Optional Boolean value indicating if the fetch must produce an object. + * @param init Optional fetch init data. Refer to the fecth() documentation for information. + * @param processFn Optional processing function that must return the configuration data as an object. */ - export interface IEnvironment { - /** - * The current environment (represented by an environment definition). - */ - readonly current: IEnvironmentDefinition; - - /** - * The list of known environments (represented by a list of environment definitions). - */ - readonly all: string[]; - - /** - * Tests the current environment definition for the presence of the specified traits. It will return true - * only if all specified traits are present; othewise it will return false. - * @param traits The environment traits expected to be found in the current environment definition. - */ - hasTraits(traits: Traits): boolean; - - /** - * Tests the current environment definition for the presence of any of the specified traits. It will return - * true if any of the specified traits is present. If none of the specified traits are present, then the - * return value will be false. - * @param traits The environments traits of which at least on of them is expected to be found in the current - * environment definition. - */ - hasAnyTrait(traits: Traits): boolean; - [x: string | 'current' | 'all' | 'hasTraits' | 'hasAnyTrait']: EnvironmentTest | IEnvironmentDefinition | string[] | ((traits: Traits) => boolean) - } - + addFetched>(request: RequestInfo | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder>; /** - * Environment interface that describes the environment object created with default environment names. + * Adds the specified JSON string to the collection of data sources that will be used to build the + * configuration object. + * @param json The JSON string to parse into a JavaScript object, or a function that returns said string. + * @param jsonParser Optional JSON parser. If not specified, the built-in JSON object will be used. + * @param reviver Optional reviver function. For more information see the JSON.parse() documentation. */ - export interface IDefaultEnvironment extends IEnvironment { - /** - * Tests if the current environment is the Development environment. - */ - isDevelopment: EnvironmentTest; - - /** - * Tests if the current environment is the PreProduction environment. - */ - isPreProduction: EnvironmentTest; - - /** - * Tests if the current environment is the Production environment. - */ - isProduction: EnvironmentTest - } - + addJson>(json: string | (() => Promise), jsonParser?: IJsonParser, reviver?: (this: any, key: string, value: any) => any): IBuilder>; /** - * Type of function used as route values when calling a URL-building function. + * Adds a single value to the collection of data sources that will be used to build the configuration object. + * @param path Key comprised of names that determine the hierarchy of the value. + * @param value Value of the property. + * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. */ - export type RouteValuesFunction = (name: string) => string - + addSingleValue(path: TKey, value?: TValue, hierarchySeparator?: TSep): IBuilder>>; /** - * Type that describes the possible ways of passing route values when calling a URL-building function. + * Adds a single value to the collection of data sources that will be used to build the configuration object. + * @param dataFn Function that returns the [key, value] tuple that needs to be added. + * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. */ - export type RouteValues = RouteValuesFunction | { [x: string]: string } - + addSingleValue(dataFn: () => Promise, hierarchySeparator?: TSep): IBuilder>>; + /** + * Sets the data source name of the last data source added to the builder. + * @param name Name for the data source. + */ + name(name: string): IBuilder; + /** + * Makes the last-added data source conditionally inclusive. + * @param predicate Predicate function that is run whenever the build function runs. If the predicate returns + * true, then the data source will be included; if it returns false, then the data source is skipped. + * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a + * shortcut to include a call to the name() function. Equivalent to when().name(). + */ + when(predicate: () => boolean, dataSourceName?: string): IBuilder; + /** + * Adds the provided environment object as a property of the final configuration object. + * @param env Previously created environment object. + * @param propertyName Optional property name for the environment object. + */ + includeEnvironment(env: IEnvironment, propertyName?: TEnvironmentKey): IEnvAwareBuilder & IncludeEnvironment>; /** - * Type that describes the possible ways of specifing a query string when calling a URL-building function. + * Creates URL functions in the final configuration object for URL's defined according to the wj-config standard. + * @param wsPropertyNames Optional list of property names whose values are expected to be objects that contain + * host, port, scheme or root path data at some point in their child hierarchy. If not provided, then the default + * list will be used. It can also be a single string, which is the same as a 1-element array. + * @param routeValueRegExp Optional regular expression used to identify replaceable route values. If this is not + * provided, then the default regular expression will match route values of the form {}, such as + * {code} or {id}. */ - export type QueryString = (() => string | ICoreConfig) | string | ICoreConfig + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValueRegExp?: RegExp): IBuilder & UrlBuilderSectionWithCheck>; + /** + * Asynchronously builds the final configuration object. + */ + build(traceValueSources?: boolean): Promise; +} +export interface IEnvAwareBuilder = {}> { /** - * Defines the capabilities exposed by non-leaf nodes in a webservices hierarchy. + * Adds the provided data source to the collection of data sources that will be used to build the + * configuration object. + * @param dataSource The data source to include. */ - export interface IWsPath extends ICoreConfig { - rootPath?: string; - _rootPath: () => string; - buildUrl: (url: string, replaceValues?: RouteValues, queryString?: QueryString) => string; - } + add>(dataSource: IDataSource): IEnvAwareBuilder>; + /** + * Adds the specified object to the collection of data sources that will be used to build the configuration object. + * @param obj Data object to include as part of the final configuration data, or a function that returns said + * object. + */ + addObject>(obj: NewT | (() => Promise)): IEnvAwareBuilder>; + /** + * Adds the specified dictionary to the collection of data sources that will be used to build the configuration + * object. + * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, + * or a function that returns said object. + * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. + * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the + * prefix is always removed after the dictionary is processed. If no prefix is provided, then all dictionary + * entries will contribute to the configuration data. + */ + addDictionary, TSep extends string = ':'>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IEnvAwareBuilder>>; + /** + * Adds the specified dictionary to the collection of data sources that will be used to build the configuration + * object. + * @param dictionary Dictionary object to include (after processing) as part of the final configuration data, + * or a function that returns said object. + * @param hierarchySeparator Optional hierarchy separator. If none is specified, a colon (:) is assumed. + * @param predicate Optional predicate function that is called for every property in the dictionary. Only when + * the return value of the predicate is true the property is included in configuration. + */ + addDictionary, TSep extends string = ':'>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IEnvAwareBuilder>>; + /** + * Adds the qualifying environment variables to the collection of data sources that will be used to build the + * configuration object. + * @param env Environment object containing the environment variables to include in the configuration, or a + * function that returns said object. + * @param prefix Optional prefix. Only properties that start with the specified prefix are included, and the + * prefix is always removed after processing. To avoid exposing non-application data as configuration, a prefix + * is always used. If none is specified, the default prefix is OPT_. + */ + addEnvironment, TPrefix extends string = 'OPT_'>(env: Record | (() => Promise>), prefix?: string): IEnvAwareBuilder>>; + /** + * Adds a fetch operation to the collection of data sources that will be used to build the configuration object. + * @param url URL to fetch. + * @param required Optional Boolean value indicating if the fetch must produce an object. + * @param init Optional fetch init data. Refer to the fecth() documentation for information. + * @param processFn Optional processing function that must return the configuration data as an object. + */ + addFetched>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder>; + /** + * Adds a fetch operation to the collection of data sources that will be used to build the configuration + * object. + * @param request Request object to use when fetching. Refer to the fetch() documentation for information. + * @param required Optional Boolean value indicating if the fetch must produce an object. + * @param init Optional fetch init data. Refer to the fecth() documentation for information. + * @param processFn Optional processing function that must return the configuration data as an object. + */ + addFetched>(request: RequestInfo | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder>; + /** + * Adds the specified JSON string to the collection of data sources that will be used to build the + * configuration object. + * @param json The JSON string to parse into a JavaScript object, or a function that returns said string. + * @param jsonParser Optional JSON parser. If not specified, the built-in JSON object will be used. + * @param reviver Optional reviver function. For more information see the JSON.parse() documentation. + */ + addJson>(json: string | (() => Promise), jsonParser?: IJsonParser, reviver?: (this: any, key: string, value: any) => any): IEnvAwareBuilder>; + /** + * Adds a single value to the collection of data sources that will be used to build the configuration object. + * @param path Key comprised of names that determine the hierarchy of the value. + * @param value Value of the property. + * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. + */ + addSingleValue(path: string, value?: ConfigurationValue, hierarchySeparator?: string): IEnvAwareBuilder>>; + /** + * Adds a single value to the collection of data sources that will be used to build the configuration object. + * @param dataFn Function that returns the [key, value] tuple that needs to be added. + * @param hierarchySeparator Optional hierarchy separator. If not specified, colon (:) is assumed. + */ + addSingleValue(dataFn: () => Promise<[string, ConfigurationValue]>, hierarchySeparator?: string): IEnvAwareBuilder>>; + /** + * Sets the data source name of the last data source added to the builder. + * @param name Name for the data source. + */ + name(name: string): IEnvAwareBuilder; + /** + * Special function that allows the developer the opportunity to add one data source per defined environment. + * + * The function iterates through all possible environments and calls the addDs function for each one. It is + * assumed that the addDs function will add zero or one data source. To signal no data source was added, + * addDs must return the boolean value "false". + * @param addDs Function that is meant to add a single data source of any type that is associated to the + * provided environment name. + */ + addPerEnvironment>(addDs: (builder: IEnvAwareBuilder, envName: TEnvironments) => boolean | string): IEnvAwareBuilder>; + /** + * Makes the last-added data source conditionally inclusive. + * @param predicate Predicate function that is run whenever the build function runs. If the predicate returns + * true, then the data source will be included; if it returns false, then the data source is skipped. + * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a + * shortcut to include a call to the name() function. Equivalent to when().name(). + */ + when(predicate: Predicate | undefined>, dataSourceName?: string): IEnvAwareBuilder; + /** + * Makes the last-added data source conditionally included only if the current environment possesses all of + * the listed traits. + * @param traits The traits the current environment must have for the data source to be added. + * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a + * shortcut to include a call to the name() function. Equivalent to whenAllTraits().name(). + */ + whenAllTraits(traits: Traits, dataSourceName?: string): IEnvAwareBuilder; + /** + * Makes the last-added data source conditionally included if the current environment possesses any of the + * listed traits. Only one coincidence is necessary. + * @param traits The list of possible traits the current environment may have in order for the data source to + * be included. + * @param dataSourceName Optional data source name. Provided to simplify the build chain and is merely a + * shortcut to include a call to the name() function. Equivalent to whenAnyTrait().name(). + */ + whenAnyTrait(traits: Traits, dataSourceName?: string): IEnvAwareBuilder; + /** + * Makes the last-added data source conditionally included if the current environment's name is equal to the + * provided environment name. + * @param envName The environment name to use to conditionally include the last-added data source. + * @param dataSourceName Optional data source name. Provided to simplify the build chain and is + * merely a shortcut to include a call to the name() function. + */ + forEnvironment(envName: TEnvironments, dataSourceName?: string): IEnvAwareBuilder; + /** + * Creates URL functions in the final configuration object for URL's defined according to the wj-config standard. + * @param wsPropertyNames Optional list of property names whose values are expected to be objects that contain + * host, port, scheme or root path data at some point in their child hierarchy. If not provided, then the default + * list will be used. It can also be a single string, which is the same as a 1-element array. + * @param routeValueRegExp Optional regular expression used to identify replaceable route values. If this is not + * provided, then the default regular expression will match route values of the form {}, such as + * {code} or {id}. + */ + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValueRegExp?: RegExp): IEnvAwareBuilder & UrlBuilderSectionWithCheck>; + /** + * Asynchronously builds the final configuration object. + */ + build(traceValueSources?: boolean, enforcePerEnvironmentCoverage?: boolean): Promise; +} +/** + * Function type that allows environment objects to declare testing functions, such as isProduction(). + */ +export type EnvironmentTestFn = () => boolean; + +/** + * Defines the capabilities required from environment objects. + */ +export type IEnvironment = { + [K in TEnvironments as `is${Capitalize}`]: EnvironmentTestFn; +} & { /** - * Defines the capabilities exposed by non-leaf nodes in a webservices hierarchy that are the parent (or root) of all - * other webservice nodes in the hierarchy. + * The current environment (represented by an environment definition). */ - export interface IWsParent extends IWsPath { - host?: string; - port?: number; - scheme?: string; - } + readonly current: IEnvironmentDefinition>; /** - * Environment class used to provide environment information through the configuration object. - */ - export class Environment { - /** - * Current environment. - */ - readonly current: IEnvironmentDefinition; - - /** - * List of all defined environments. - */ - readonly all: string[]; - [x: string]: (() => boolean) | string | string[]; - - /** - * Initializes a new instance of this class. - * @param currentEnvironment The current environment name or the current environment as an - * IEnvironmentDefinition object. - * @param possibleEnvironments The list of all possible environment names. It is used to create the - * environment test functions and to validate that the current environment is part of this list, minimizing - * the possibility of a mispelled name. - */ - constructor(currentEnvironment: string | IEnvironmentDefinition, possibleEnvironments?: string[]); - } + * The list of known environments (represented by a list of environment definitions). + */ + readonly all: TEnvironments[]; /** - * Environment definition class used to specify the current environment as an object. + * Tests the current environment definition for the presence of the specified traits. It will return true + * only if all specified traits are present; othewise it will return false. + * @param traits The environment traits expected to be found in the current environment definition. */ - export class EnvironmentDefinition { - public readonly name: string; - public readonly traits: Traits; - /** - * Initializes a new instance of this class. - * @param name The name of the current environment. - * @param traits The traits assigned to the current environment. - */ - constructor(name: string, traits?: Traits); - } + hasTraits(traits: Traits): boolean; /** - * Type that defines the acceptable trait types for a single trait. It is encouraged to use number-based traits. + * Tests the current environment definition for the presence of any of the specified traits. It will return + * true if any of the specified traits is present. If none of the specified traits are present, then the + * return value will be false. + * @param traits The environments traits of which at least on of them is expected to be found in the current + * environment definition. */ - export type Trait = number | string; + hasAnyTrait(traits: Traits): boolean; +} + +/** + * Type used to determine if a configuration node is an appropriate URL root node. + */ +export type HasHost = { + host: string; +} +/** + * Type used to determine if a configuration node holds path information. + */ +export type HasRootPath = { + rootPath: string; +} + +/** + * Defines the reserved property names for the URL-building feature of the builders. + */ +export type UrlSectionReserved = { + host?: string; + port?: number; + scheme?: string; + rootPath?: string; +}; + +/** + * Type of function used as route values when calling a URL-building function. + */ +export type RouteValuesFn = (name: string) => any; + +/** + * Defines the acceptable route value replacement types. + */ +export type RouteReplacementArg = Record | any[] | RouteValuesFn; + +/** + * Defines the acceptable query string argument types. + */ +export type QueryStringArg = Record | string | (() => string | Record); + +/** + * Defines the shape of the dynamically-created URL-building functions. + */ +export type UrlBuilderFn = (routeValues?: RouteReplacementArg, queryString?: QueryStringArg) => string; + +/** + * Defines the signature of the `buildUrl` functions that get created in the configuration object when using the + * builder's `createUrlFunctions()` method. + * @param path The extra path to add to the built URL. + * @param routeValues Optional argument used to perform route value replacement. + * @param queryString Optional argument used to append query string data to the generated URL. + */ +export type BuildUrlFn = (path: string, routeValues?: RouteReplacementArg, queryString?: QueryStringArg) => string; + +/** + * Defines the shape of nodes in the configuration object that have been successfully processed and converted to + * provide URL-building functions. + */ +export type UrlNode = Partial & { + buildUrl: BuildUrlFn; /** - * Type that defines the acceptable trait types for multiple traits. It is encouraged to use number-based traits. + * Calculates the accumulated root path for this node. + * @returns The accumulated root path for this node as a string. */ - export type Traits = number | string[]; + _rootPath: () => string; +} & { + [x: string]: UrlBuilderFn; +}; + +export type UrlRoot = UrlSectionReserved & UrlNode; + +/** + * Defines how url-building sections of the configuration are transformed by `createUrlFunctions()`. + */ +export type UrlBuilderSection = { + buildUrl: (extraPath: string, routeValues?: RouteReplacementArg, queryString?: QueryStringArg) => string; +} & { + [K in TUrl]: K extends keyof UrlSectionReserved ? UrlSectionReserved[K] : + K extends `_${string}` ? T[K] : + T[K] extends string ? UrlBuilderFn : + T[K] extends Record ? UrlBuilderSection : + T[K] +}; + +/** + * Defines how url-building sections of the configuration are transformed by `createUrlFunctions()` while traversing the + * configuration tree in the looks for `rootPath` or `host`. + */ +export type UrlBuilderSectionWithCheck = T extends HasHost ? + UrlBuilderSection : + T extends HasRootPath ? + UrlBuilderSection : + T extends Record ? + { + [K in TUrl]: UrlBuilderSectionWithCheck + } + : T; + +/** + * Type that defines the acceptable trait types for a single trait. It is encouraged to use number-based traits. + */ +export type Trait = number | string; + +/** + * Type that defines the acceptable trait types for multiple traits. It is encouraged to use number-based traits. + */ +export type Traits = number | string[]; + +/** + * Defines the capabilities required from objects used as environment definitions. + */ +export interface IEnvironmentDefinition { /** - * Defines the capabilities required from objects used as environment definitions. + * Gets the environment's name. */ - export interface IEnvironmentDefinition { - /** - * Gets the environment's name. - */ - readonly name: string, + readonly name: TEnvironments, - /** - * Gets the environment's traits. - */ - readonly traits: Traits - } + /** + * Gets the environment's traits. + */ + readonly traits: Traits } diff --git a/tests/DataSource.test.js b/tests/DataSource.test.js index e446b6b..d6386ce 100644 --- a/tests/DataSource.test.js +++ b/tests/DataSource.test.js @@ -1,5 +1,5 @@ import 'chai/register-expect.js'; -import { DataSource } from '../out/DataSource.js'; +import { DataSource } from '../out/dataSources/DataSource.js'; describe('DataSource', () => { it('Should make the name given during construction available through the "name" property.', () => { diff --git a/tests/DictionaryDataSource.test.js b/tests/DictionaryDataSource.test.js index 24ef3c7..bb4e9ee 100644 --- a/tests/DictionaryDataSource.test.js +++ b/tests/DictionaryDataSource.test.js @@ -1,5 +1,5 @@ import 'chai/register-expect.js'; -import DictionaryDataSource from '../out/DictionaryDataSource.js'; +import { DictionaryDataSource } from '../out/dataSources/DictionaryDataSource.js'; describe('DictionaryDataSource', () => { it('Should name itself as "Dictionary" upon construction.', () => { @@ -59,12 +59,6 @@ describe('DictionaryDataSource', () => { target: 'dictionary', text: 'an array' }, - { - dic: () => false, - sep: ':', - target: 'dictionary', - text: 'a function' - }, { dic: null, sep: ':', @@ -77,6 +71,12 @@ describe('DictionaryDataSource', () => { target: 'dictionary', text: 'undefined' }, + { + dic: { a: { b: 1 }}, + sep: ':', + target: 'dictionary', + text: 'a non-flat object' + }, { dic: { 'key': 'value' }, sep: '', @@ -129,21 +129,152 @@ describe('DictionaryDataSource', () => { failedConstructionTests.forEach(t => { it(`Should throw an error if constructed with ${t.text} for ${t.target}.`, () => failedConstructionTest(t.dic, t.sep)); }); + const successfulConstructionTests = [ + { + dic: { a: 'b' }, + sep: ':', + text: 'a flat object', + target: 'dictionary', + }, + { + dic: { a: 'b' }, + sep: ':', + text: '":"', + target: 'hierarchy separator', + }, + { + dic: { a: 'b' }, + sep: '_', + text: '"_"', + target: 'hierarchy separator', + }, + { + dic: { a: 'b' }, + sep: '__', + text: '"__"', + target: 'hierarchy separator', + }, + { + dic: { a: 'b' }, + sep: '_sep_', + text: '"_sep_"', + target: 'hierarchy separator', + }, + ]; + const successfulConstructionTest = (dic, sep) => { + // Act. + const dds = new DictionaryDataSource(dic, sep); + + // Assert. + expect(!!dds).to.be.true; + }; + successfulConstructionTests.forEach(t => { + it(`Should successfully construct with ${t.text} for ${t.target}.`, () => successfulConstructionTest(t.dic, t.sep)); + }); + const prefixTests = [ + { + prefix: 'ThePrefix_', + succeeds: true, + }, + { + prefix: (n) => !!n, + text: 'a function', + succeeds: true, + }, + { + prefix: true, + succeeds: false, + }, + { + prefix: false, + succeeds: false, + }, + { + prefix: 1, + succeeds: false, + }, + { + prefix: new Date(), + text: 'a date object', + succeeds: false, + }, + { + prefix: new Map(), + text: 'a map object', + succeeds: false, + }, + { + prefix: new Set(), + text: 'a set object', + succeeds: false, + }, + { + prefix: new WeakMap(), + text: 'a weak map object', + succeeds: false, + }, + { + prefix: '', + text: 'an empty string', + succeeds: false, + }, + ]; + const prefixTest = (prefix, succeeds) => { + // Act. + const act = () => new DictionaryDataSource({ a: 1 }, ':', prefix); + + // Assert. + let expectation = expect(act); + if (succeeds) { + expectation = expectation.not; + } + expectation.to.throw(); + }; + prefixTests.forEach(t => { + it(`Should ${t.succeeds ? 'succeed' : 'fail'} when trying to construct the data source with "${t.text ?? t.prefix}" as prefix.`, () => prefixTest(t.prefix, t.succeeds)); + }); + const validationWithPrefixTests = [ + { + dic: { p_a: 1, b: 2, c: { c_p: true } }, + prefix: 'p_', + }, + { + dic: { a: 1, b: 2, invalid: { c_p: true } }, + prefix: (n) => n !== 'invalid', + text: 'a function' + }, + ]; + const validationWithPrefixTest = (dic, prefix) => { + // Act. + const act = () => new DictionaryDataSource(dic, ':', prefix); + + // Assert. + expect(act).not.to.throw(); + }; + validationWithPrefixTests.forEach(t => { + it(`Should validate the dictionary using "${t.text ?? t.prefix}" as prefix.`, () => validationWithPrefixTest(t.dic, t.prefix)); + }); describe('getObject', () => { - it('Should throw an error if the provided dictionary was not a flat object.', () => { + it('Should throw an error if the provided dictionary function returns a non-flat object.', async () => { // Arrange. const dic = { prop1: 123, prop2: { prop3: 'abc' } }; - const ds = new DictionaryDataSource(dic, ':'); + const ds = new DictionaryDataSource(() => dic, ':'); + let didThrow = false; // Act. - const act = async () => await ds.getObject(); + try { + await ds.getObject(); + } + catch { + didThrow = true; + } // Assert. - expect(act).to.throw(Error); + expect(didThrow).to.equal(true); }); const successfulResultsTest = async (dic, expectedResult, prefix) => { // Arrange. diff --git a/tests/Environment.test.js b/tests/buildEnvironment.test.js similarity index 89% rename from tests/Environment.test.js rename to tests/buildEnvironment.test.js index 1c96b70..fab7d87 100644 --- a/tests/Environment.test.js +++ b/tests/buildEnvironment.test.js @@ -1,6 +1,6 @@ import 'chai/register-expect.js'; -import { isFunction, forEachProperty, isConfig } from '../out/helpers.js'; -import { Environment } from '../out/Environment.js'; +import { buildEnvironment } from '../out/buildEnvironment.js'; +import { forEachProperty, isConfigNode, isFunction } from '../out/helpers.js'; const testEnvNames = [ 'Dev', @@ -8,13 +8,13 @@ const testEnvNames = [ 'Prod' ]; -describe('Environment', () => { +describe('buildEnvironment', () => { const testErrorFn = (envNames) => { // Arrange. const envName = 'Dev'; // Act. - const act = () => new Environment(envName, envNames); + const act = () => buildEnvironment(envName, envNames); // Assert. expect(act).to.throw(Error); @@ -28,10 +28,10 @@ describe('Environment', () => { const envName = 'Dev'; // Act. - const env = new Environment(envName, testEnvNames); + const env = buildEnvironment(envName, testEnvNames); // Assert. - expect(isConfig(env.current)).to.be.true; + expect(isConfigNode(env.current)).to.be.true; expect(env.current.name).to.equal(envName); }); it('Should save the provided environment names in the all property.', () => { @@ -39,7 +39,7 @@ describe('Environment', () => { const envName = 'Dev'; // Act. - const env = new Environment(envName, testEnvNames); + const env = buildEnvironment(envName, testEnvNames); // Assert. expect(env.all).to.have.same.members(testEnvNames); @@ -49,7 +49,7 @@ describe('Environment', () => { const envName = 'Dev'; // Act. - const env = new Environment(envName, testEnvNames); + const env = buildEnvironment(envName, testEnvNames); // Assert. const foundFns = []; @@ -63,7 +63,7 @@ describe('Environment', () => { }); const missingEnvNameTest = (envDef) => { // Act. - const act = () => new Environment(envDef, testEnvNames); + const act = () => buildEnvironment(envDef, testEnvNames); // Assert. expect(act).to.throw(Error); @@ -73,7 +73,7 @@ describe('Environment', () => { describe('hasTraits', () => { const traitMismatchTest = (envDef, testTraits) => { // Arrange. - const env = new Environment(envDef, testEnvNames); + const env = buildEnvironment(envDef, testEnvNames); // Act. const act = () => env.hasTraits(testTraits); @@ -86,7 +86,7 @@ describe('Environment', () => { it('Should throw if given an array of string test traits when the current environment traits are of the numeric kind.', () => traitMismatchTest({ name: 'Dev', traits: 3 }, ['abc', 'def'])); const runTestFn = (envDef, testTraits, expectedResult) => { // Arrange. - const env = new Environment(envDef, testEnvNames); + const env = buildEnvironment(envDef, testEnvNames); // Act. const result = env.hasTraits(testTraits); @@ -108,7 +108,7 @@ describe('Environment', () => { describe('hasAnyTrait', () => { const traitMismatchTest = (envDef, testTraits) => { // Arrange. - const env = new Environment(envDef, testEnvNames); + const env = buildEnvironment(envDef, testEnvNames); // Act. const act = () => env.hasAnyTrait(testTraits); @@ -120,7 +120,7 @@ describe('Environment', () => { it('Should throw if given a string test trait when the current environment traits are of the numeric kind.', () => traitMismatchTest({ name: 'Dev', traits: 3 }, ['abc', 'def'])); const runTestFn = (envDef, testTraits, expectedResult) => { // Arrange. - const env = new Environment(envDef, testEnvNames); + const env = buildEnvironment(envDef, testEnvNames); // Act. const result = env.hasAnyTrait(testTraits); diff --git a/tests/helpers.test.js b/tests/helpers.test.js index b140e06..8ac3711 100644 --- a/tests/helpers.test.js +++ b/tests/helpers.test.js @@ -1,5 +1,5 @@ import 'chai/register-expect.js'; -import { isArray, isConfig, isFunction, forEachProperty, attemptParse } from '../out/helpers.js'; +import { attemptParse, forEachProperty, isArray, isConfigNode, isFunction } from '../out/helpers.js'; describe('helpers', () => { describe('isArray', () => { @@ -23,17 +23,16 @@ describe('helpers', () => { it('Should return false if the given object is an empty object.', () => testFn({}, false)); it('Should return false if the given object is an object.', () => testFn({ a: 'b' }, false)); }); - describe('isObject', () => { + describe('isConfigNode', () => { const testFn = (testObj, expectedResult) => { // Act. - const result = isConfig(testObj); + const result = isConfigNode(testObj); // Assert. expect(result).to.equal(expectedResult); }; it('Should return false if the given object is an empty array.', () => testFn([], false)); it('Should return false if the given object is an array.', () => testFn([1, 'A'], false)); - it('Should return true if the given object is null.', () => testFn(null, true)); it('Should return false if the given object is undefined.', () => testFn(undefined, false)); it('Should return false if the given object is a number.', () => testFn(1, false)); it('Should return false if the given object is a string.', () => testFn('ABC', false)); diff --git a/tests/index.test.js b/tests/index.test.js index 72154d1..f09b112 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -1,20 +1,16 @@ import 'chai/register-expect.js'; -import Builder from '../out/Builder.js'; +import { Builder } from '../out/builders/Builder.js'; import * as allExports from '../out/index.js'; describe('All Exports', () => { - it('Should export the Environment class.', () => { + it('Should export buildEnvironment function.', () => { // Assert. - expect(allExports.Environment).to.exist; + expect(allExports.buildEnvironment).to.exist; }); it('Should export the EnvironmentDefinition class.', () => { // Assert. expect(allExports.EnvironmentDefinition).to.exist; }); - it('Should export the DataSource class.', () => { - // Assert. - expect(allExports.DataSource).to.exist; - }); it('Should export the entry function as default.', () => { // Assert. expect(allExports.default).to.exist; diff --git a/tests/makeWsUrlFunctions.test.js b/tests/makeWsUrlFunctions.test.js index 26589c4..e9dc25e 100644 --- a/tests/makeWsUrlFunctions.test.js +++ b/tests/makeWsUrlFunctions.test.js @@ -1,5 +1,5 @@ import 'chai/register-expect.js'; -import { isConfig, forEachProperty } from '../out/helpers.js'; +import { forEachProperty, isConfigNode } from '../out/helpers.js'; import makeWsUrlFunctions from '../out/makeWsUrlFunctions.js'; const propertyInHierarchy = (obj, propertyName, result, resultKey) => { @@ -7,7 +7,7 @@ const propertyInHierarchy = (obj, propertyName, result, resultKey) => { result = {}; } forEachProperty(obj, (key, value) => { - if (isConfig(value)) { + if (isConfigNode(value)) { const newKey = resultKey ? `${resultKey}_${key}` : key; propertyInHierarchy(value, propertyName, result, newKey); } diff --git a/tests/package-lock.json b/tests/package-lock.json index 12bfa6e..e68cf24 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -1,7 +1,7 @@ { "name": "wj-config-tests", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,21 +9,24 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "chai": "^4.3.7", + "@types/chai": "^5.0.0", + "chai": "^5.1.2", "mocha": "^10.0.0" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true + "node_modules/@types/chai": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.0.tgz", + "integrity": "sha512-+DwhEHAaFPPdJ2ral3kNHFQXnTfscEEFsUxzD+d7nlcLrFK23JtNjH71RGasTcHb88b4vVi4mTyfpf8u2L8bdA==", + "dev": true, + "license": "MIT" }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -33,6 +36,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -42,6 +46,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -53,10 +58,11 @@ } }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -69,30 +75,37 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -100,17 +113,19 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -120,13 +135,15 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -135,21 +152,20 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -157,6 +173,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -173,6 +190,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -181,25 +199,21 @@ } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -212,6 +226,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -221,6 +238,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -232,6 +250,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -243,21 +262,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -268,17 +283,12 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -287,22 +297,21 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -311,13 +320,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -327,6 +338,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -335,10 +347,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -351,6 +364,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -367,6 +381,7 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } @@ -375,14 +390,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -396,34 +413,27 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -434,6 +444,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -441,33 +452,12 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -477,6 +467,7 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, + "license": "MIT", "bin": { "he": "bin/he" } @@ -485,7 +476,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -495,13 +488,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -514,6 +509,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -523,6 +519,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -532,6 +529,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -544,6 +542,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -553,6 +552,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -562,6 +562,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -574,6 +575,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -586,6 +588,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -601,6 +604,7 @@ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -613,19 +617,18 @@ } }, "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } + "license": "MIT" }, "node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -634,33 +637,32 @@ } }, "node_modules/mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -668,35 +670,21 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -706,6 +694,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -715,6 +704,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -730,6 +720,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -745,26 +736,19 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/picomatch": { @@ -772,6 +756,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -784,6 +769,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -793,6 +779,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -805,6 +792,7 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -827,13 +815,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -843,6 +833,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -857,6 +848,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -869,6 +861,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -881,6 +874,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -896,6 +890,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -903,26 +898,19 @@ "node": ">=8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -939,13 +927,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -955,6 +945,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -969,10 +960,11 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -982,6 +974,7 @@ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, + "license": "MIT", "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -997,6 +990,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1004,716 +998,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/tests/package.json b/tests/package.json index 5029371..7289116 100644 --- a/tests/package.json +++ b/tests/package.json @@ -9,7 +9,8 @@ "author": "José Pablo Ramírez Vargas ", "license": "ISC", "devDependencies": { - "chai": "^4.3.7", + "@types/chai": "^5.0.0", + "chai": "^5.1.2", "mocha": "^10.0.0" }, "type": "module" diff --git a/tsconfig.json b/tsconfig.json index 0436aae..1f16cbc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,8 +39,8 @@ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ @@ -90,9 +90,9 @@ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": false /* Skip type checking all .d.ts files. */ }, "include": [ - "./src/*.ts" + "./src/**/*.ts" ] } \ No newline at end of file