From 158094f5862ac5feb2aff03d571b310b0d3e8b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Mon, 21 Oct 2024 01:39:14 -0600 Subject: [PATCH 01/15] WIP: Initial TypeScript re-write --- package-lock.json | 358 +++++++++++++++++- package.json | 3 +- src/Builder.ts | 137 ++++--- src/DictionaryDataSource.ts | 71 ++-- src/Environment.ts | 59 +-- src/EnvironmentDataSource.ts | 6 +- src/EnvironmentDefinition.ts | 6 +- src/FetchedDataSource.ts | 12 +- src/JsonDataSource.ts | 4 +- src/Merge.ts | 35 +- src/ObjectDataSource.ts | 16 +- src/SingleValueDataSource.ts | 14 +- src/helpers.ts | 33 +- src/makeWsUrlFunctions.ts | 73 ++-- src/package-lock.json | 24 +- src/package.json | 16 +- src/wj-config.d.ts | 712 +++++++++++++++++------------------ tsconfig.json | 2 +- 18 files changed, 982 insertions(+), 599 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34b99a6..b23626d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,14 +5,209 @@ "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.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" + }, + "node_modules/publint": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.11.tgz", + "integrity": "sha512-/kxbd+sD/uEG515N/ZYpC6gYs8h89cQ4UIsAq1y6VT4qlNh8xmiSwcP2xU2MbzXFl8J0l2IdONKFweLfYoqhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "npm-packlist": "^5.1.3", + "picocolors": "^1.1.0", + "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.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20,13 +215,162 @@ "engines": { "node": ">=14.17" } + }, + "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" } }, "dependencies": { + "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 + }, + "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" + } + }, + "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 + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "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, + "requires": { + "minimatch": "^5.0.1" + } + }, + "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 + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, + "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, + "requires": { + "npm-normalize-package-bin": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + } + }, + "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" + } + }, + "picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "publint": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.11.tgz", + "integrity": "sha512-/kxbd+sD/uEG515N/ZYpC6gYs8h89cQ4UIsAq1y6VT4qlNh8xmiSwcP2xU2MbzXFl8J0l2IdONKFweLfYoqhcA==", + "dev": true, + "requires": { + "npm-packlist": "^5.1.3", + "picocolors": "^1.1.0", + "sade": "^1.8.1" + } + }, + "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, + "requires": { + "mri": "^1.1.0" + } + }, "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.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true + }, + "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 } } 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 index 7fade03..b3b872d 100644 --- a/src/Builder.ts +++ b/src/Builder.ts @@ -1,18 +1,18 @@ -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 type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "wj-config"; +import DictionaryDataSource from "./DictionaryDataSource.js"; +import { buildEnvironment } from "./Environment.js"; +import EnvironmentDataSource from "./EnvironmentDataSource.js"; import FetchedDataSource from "./FetchedDataSource.js"; -import { isConfig } from "./helpers.js" +import { isConfigNode } from "./helpers.js"; import JsonDataSource from "./JsonDataSource.js"; -import makeWsUrlFunctions from "./makeWsUrlFunctions.js" -import merge from "./Merge.js" -import { ObjectDataSource } from "./ObjectDataSource.js" +import makeWsUrlFunctions from "./makeWsUrlFunctions.js"; +import merge from "./Merge.js"; +import { ObjectDataSource } from "./ObjectDataSource.js"; import SingleValueDataSource from "./SingleValueDataSource.js"; -interface IEnvironmentSource { +interface IEnvironmentSource { name?: string, - environment: IEnvironment + environment: IEnvironment>; } interface IUrlData { @@ -20,26 +20,21 @@ interface IUrlData { routeValuesRegExp: RegExp; } -interface IDataSourceDef { - dataSource: IDataSource, - predicate?: Predicate +interface IDataSourceDef { + dataSource: IDataSource>, + predicate?: Predicate> | undefined> } -export default class Builder implements IBuilder { - /** - * Default list of property names that undergo the URL functions transformation. - */ - static readonly defaultWsPropertyNames = ['ws']; - +export default class Builder= {}, TEnvironments extends string | undefined = undefined> implements IBuilder { /** * Collection of data sources added to the builder. */ - private _dsDefs: IDataSourceDef[] = []; + private _dsDefs: IDataSourceDef[] = []; /** * Environment source. */ - private _envSource?: IEnvironmentSource; + private _envSource?: IEnvironmentSource; /** * Boolean flag used to raise an error if there was no call to includeEnvironment() when it is known to be needed. @@ -50,7 +45,7 @@ export default class Builder implements IBuilder { * 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; + private _perEnvDsCount: Record, number> | null = null; /** * URL data used to create URL functions out of specific property values in the resulting configuration object. @@ -62,40 +57,40 @@ export default class Builder implements IBuilder { */ private _lastCallWasDsAdd: boolean = false; - add(dataSource: IDataSource): IBuilder { + add>(dataSource: IDataSource) { this._dsDefs.push({ dataSource: dataSource }); dataSource.index = this._dsDefs.length - 1; this._lastCallWasDsAdd = true; - return this; + return this as unknown as IBuilder & NewT, TEnvironments>; } - addObject(obj: ICoreConfig | (() => Promise)): IBuilder { + addObject>(obj: NewT | (() => Promise)) { return this.add(new ObjectDataSource(obj)); } - addDictionary(dictionary: ICoreConfig | (() => Promise), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate): IBuilder { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); } - addEnvironment(env: ICoreConfig | (() => Promise), prefix: string = 'OPT_'): IBuilder { - return this.add(new EnvironmentDataSource(env, prefix)); + addEnvironment>(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): IBuilder { - return this.add(new FetchedDataSource(input, required, init, procesFn)); + 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)); + 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)); + addSingleValue>(path: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { + return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); } - addPerEnvironment(addDs: (builder: IBuilder, envName: string) => boolean | string): IBuilder { + addPerEnvironment>(addDs: (builder: IBuilder, envName: TEnvironments) => boolean | string) { if (!this._envSource) { throw new Error('Using addPerEnvironment() requires a prior call to includeEnvironment().'); } @@ -105,18 +100,18 @@ export default class Builder implements IBuilder { this.forEnvironment(n, typeof result === 'string' ? result : undefined); } }); - return this; + return this as unknown as IBuilder & NewT, TEnvironments>; } - name(name: string): IBuilder { + 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; - return this; + return this as unknown as IBuilder; } - when(predicate: Predicate, dataSourceName?: string): IBuilder { + when(predicate: Predicate> | undefined>, 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.'); } @@ -128,26 +123,26 @@ export default class Builder implements IBuilder { if (dataSourceName != undefined) { this.name(dataSourceName); } - return this; + return this as unknown as IBuilder; } - whenAllTraits(traits: Traits, dataSourceName?: string): IBuilder { + whenAllTraits(traits: Traits, dataSourceName?: string) { this._envIsRequired = true; return this.when(env => { - return (env as IEnvironment).hasTraits(traits); + return env!.hasTraits(traits) ?? false; }, dataSourceName); } - whenAnyTrait(traits: Traits, dataSourceName?: string): IBuilder { + whenAnyTrait(traits: Traits, dataSourceName?: string) { this._envIsRequired = true; return this.when(env => { - return (env as IEnvironment).hasAnyTrait(traits); + return env!.hasAnyTrait(traits); }, dataSourceName); } - forEnvironment(envName: string, dataSourceName?: string): IBuilder { + forEnvironment(envName: Exclude, dataSourceName?: string) { this._envIsRequired = true; - this._perEnvDsCount = this._perEnvDsCount ?? {}; + this._perEnvDsCount = this._perEnvDsCount ?? {} as Record, number>; let count = this._perEnvDsCount[envName] ?? 0; this._perEnvDsCount[envName] = ++count; dataSourceName = @@ -156,46 +151,49 @@ export default class Builder implements IBuilder { return this.when(e => e?.current.name === envName, dataSourceName); } - includeEnvironment(valueOrEnv: string | IEnvironment, propNameOrEnvNames?: string[] | string, propertyName?: string): IBuilder { + includeEnvironment( + valueOrEnv: Exclude | IEnvironment>, + propNameOrEnvNames?: Exclude[] | TEnvironmentKey, propertyName?: TEnvironmentKey + ) { 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); + 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 = valueOrEnv; + env = buildEnvironment(valueOrEnv, envNames); } this._envSource = { name: propName, environment: env }; - return this; + return this as unknown as IBuilder & IncludeEnvironment, TEnvironments>; } - createUrlFunctions(wsPropertyNames?: string | string[], routeValuesRegExp?: RegExp): IBuilder { + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { this._lastCallWasDsAdd = false; - let propNames = null; + let propNames: TUrl[]; if (typeof wsPropertyNames === 'string') { if (wsPropertyNames !== '') { propNames = [wsPropertyNames]; } } - else if (wsPropertyNames && wsPropertyNames.length > 0) { + else if (Array.isArray(wsPropertyNames) && wsPropertyNames.length > 0) { propNames = wsPropertyNames } else { - propNames = Builder.defaultWsPropertyNames; + throw new Error("The 'wsPropertyNames' property now has no default value and must be provided."); } this._urlData = { - wsPropertyNames: propNames as string[], + wsPropertyNames: propNames! as string[], routeValuesRegExp: routeValuesRegExp ?? /\{(\w+)\}/g }; - return this; + return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; } - async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true): Promise { + async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { this._lastCallWasDsAdd = false; // See if environment is required. if (this._envIsRequired && !this._envSource) { @@ -206,21 +204,21 @@ export default class Builder implements IBuilder { // 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)) { + 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; + 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; + 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. @@ -230,7 +228,7 @@ export default class Builder implements IBuilder { } }); if (qualifyingDs.length > 0) { - const dsTasks: Promise[] = []; + const dsTasks: Promise>[] = []; qualifyingDs.forEach(ds => { dsTasks.push(ds.getObject()); }); @@ -255,7 +253,7 @@ export default class Builder implements IBuilder { if (urlData) { urlData.wsPropertyNames.forEach((value) => { const obj = wjConfig[value]; - if (isConfig(obj)) { + if (isConfigNode(obj)) { makeWsUrlFunctions(obj, urlData.routeValuesRegExp, globalThis.window && globalThis.window.location !== undefined); } else { @@ -271,6 +269,7 @@ export default class Builder implements IBuilder { wjConfig._qualifiedDs = []; } } - return wjConfig; + return wjConfig as T; } }; + diff --git a/src/DictionaryDataSource.ts b/src/DictionaryDataSource.ts index 2a8bcf0..4a1ec68 100644 --- a/src/DictionaryDataSource.ts +++ b/src/DictionaryDataSource.ts @@ -1,6 +1,6 @@ -import type { ConfigurationValue, ICoreConfig, IDataSource, Predicate } from "wj-config"; +import type { ConfigurationNode, ConfigurationValue, Dictionary, IDataSource, Predicate } from "wj-config"; import { DataSource } from "./DataSource.js"; -import { attemptParse, forEachProperty, isConfig } from "./helpers.js"; +import { attemptParse, forEachProperty, isConfigNode } from "./helpers.js"; const processKey = (key: string, hierarchySeparator: string, prefix?: string) => { if (prefix) { @@ -9,35 +9,35 @@ const processKey = (key: string, hierarchySeparator: string, prefix?: string) => return key.split(hierarchySeparator); }; -const ensurePropertyValue = (obj: ICoreConfig, name: string) => { +const ensurePropertyValue = (obj: ConfigurationNode, 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; +export default class DictionaryDataSource> extends DataSource implements IDataSource { + #dictionary: Record | (() => Promise>); + #hierarchySeparator: string; + #prefixOrPredicate?: string | Predicate; #buildPredicate(): [Predicate, string] { - let predicateFn: Predicate = name => true; + let predicateFn: Predicate = _ => true; let prefix: string = ''; - if (this._prefixOrPredicate) { - if (typeof this._prefixOrPredicate === "string") { - prefix = this._prefixOrPredicate; + if (this.#prefixOrPredicate) { + if (typeof this.#prefixOrPredicate === "string") { + prefix = this.#prefixOrPredicate; predicateFn = name => name.startsWith(prefix); } else { - predicateFn = this._prefixOrPredicate; + predicateFn = this.#prefixOrPredicate; } } return [predicateFn, prefix]; } - #validateDictionary(dic: ICoreConfig) { - if (!isConfig(dic)) { + #validateDictionary(dic: unknown) { + if (!isConfigNode(dic)) { throw new Error('The provided dictionary must be a flat object.'); } const [predicateFn, prefix] = this.#buildPredicate(); @@ -46,34 +46,36 @@ export default class DictionaryDataSource extends DataSource implements IDataSou // This property does not qualify, so skip its validation. return false; } - if (isConfig(v)) { + if (isConfigNode(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)) { + #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 (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; + // 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) { - 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.`); + 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 as ICoreConfig)[keyParts[keyParts.length - 1]]) { + 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 @@ -81,13 +83,13 @@ export default class DictionaryDataSource extends DataSource implements IDataSou if (typeof value === 'string') { value = attemptParse(value); } - (obj as ICoreConfig)[keyParts[keyParts.length - 1]] = value; + obj[keyParts[keyParts.length - 1]] = value; } }); return result; } - constructor(dictionary: ICoreConfig | (() => Promise), hierarchySeparator: string, prefixOrPredicate?: string | Predicate) { + constructor(dictionary: Dictionary | (() => Promise), hierarchySeparator: string, prefixOrPredicate?: string | Predicate) { super('Dictionary'); if (!hierarchySeparator) { throw new Error('Dictionaries must specify a hierarchy separator.'); @@ -95,21 +97,22 @@ export default class DictionaryDataSource extends DataSource implements IDataSou if (typeof hierarchySeparator !== 'string') { throw new Error('The hierarchy separator must be a string.'); } - this._hierarchySeparator = hierarchySeparator; + 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; + this.#prefixOrPredicate = prefixOrPredicate; if (dictionary && typeof dictionary !== 'function') { this.#validateDictionary(dictionary); } - this._dictionary = dictionary; + this.#dictionary = dictionary; } - async getObject(): Promise { - let dic = this._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/Environment.ts b/src/Environment.ts index 868db1a..afa594a 100644 --- a/src/Environment.ts +++ b/src/Environment.ts @@ -1,40 +1,41 @@ -import type { EnvironmentTest, IEnvironment, IEnvironmentDefinition, Trait, Traits } from "wj-config"; +import type { EnvironmentTestFn, 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 { +function ensureEnvDefinition(env: string | IEnvironmentDefinition): IEnvironmentDefinition { if (typeof env === 'string') { - return new EnvironmentDefinition(env); + return new EnvironmentDefinition(env) as IEnvironmentDefinition; } 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) +function capitalize(text: string) { + return text[0].toLocaleUpperCase() + text.slice(1); +} - 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); - } +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; - #normalizeTestTraits(traits: Trait | Traits): Trait | Traits { + 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.'); } @@ -47,8 +48,8 @@ export class Environment implements IEnvironment { return traits; } - hasTraits(traits: Trait | Traits): boolean { - traits = this.#normalizeTestTraits(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; @@ -63,8 +64,8 @@ export class Environment implements IEnvironment { return hasStringTraits(traits as string[]); } - hasAnyTrait(traits: Trait | Traits): boolean { - traits = this.#normalizeTestTraits(traits); + 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) { diff --git a/src/EnvironmentDataSource.ts b/src/EnvironmentDataSource.ts index 1f3c7c1..bb5fe6e 100644 --- a/src/EnvironmentDataSource.ts +++ b/src/EnvironmentDataSource.ts @@ -1,8 +1,8 @@ -import type { ICoreConfig } from "wj-config"; +import type { Dictionary } from "wj-config"; import DictionaryDataSource from "./DictionaryDataSource.js"; -export default class EnvironmentDataSource extends DictionaryDataSource { - constructor(env: ICoreConfig | (() => Promise), prefix?: string) { +export default 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.'); diff --git a/src/EnvironmentDefinition.ts b/src/EnvironmentDefinition.ts index 7c954e1..346afb8 100644 --- a/src/EnvironmentDefinition.ts +++ b/src/EnvironmentDefinition.ts @@ -1,10 +1,10 @@ import type { IEnvironmentDefinition, Traits } from "wj-config"; -export class EnvironmentDefinition implements IEnvironmentDefinition { - public readonly name: string; +export class EnvironmentDefinition implements IEnvironmentDefinition { + public readonly name: TEnvironments; public readonly traits: Traits; - constructor(name: string, traits?: Traits) { + constructor(name: TEnvironments, traits?: Traits) { this.name = name; this.traits = traits ?? 0; } diff --git a/src/FetchedDataSource.ts b/src/FetchedDataSource.ts index cb35121..90b15ff 100644 --- a/src/FetchedDataSource.ts +++ b/src/FetchedDataSource.ts @@ -1,12 +1,12 @@ -import type { ICoreConfig, ProcessFetchResponse } from "wj-config"; +import type { ConfigurationNode, ProcessFetchResponse } from "wj-config"; import { DataSource } from "./DataSource.js"; -export default class FetchedDataSource extends DataSource { +export default 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/JsonDataSource.ts index 3bbef44..47a69d4 100644 --- a/src/JsonDataSource.ts +++ b/src/JsonDataSource.ts @@ -1,6 +1,6 @@ import { DataSource } from "./DataSource.js"; -export default class JsonDataSource extends DataSource { +export default 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/Merge.ts b/src/Merge.ts index 10eb403..0d68ae8 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 type { ConfigurationNode, IDataSource, IDataSourceInfo, Trace } from "wj-config"; +import { forEachProperty, isArray, isConfigNode } from "./helpers.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/ObjectDataSource.ts b/src/ObjectDataSource.ts index 5ab9b4f..61002af 100644 --- a/src/ObjectDataSource.ts +++ b/src/ObjectDataSource.ts @@ -1,18 +1,18 @@ -import type { ICoreConfig, IDataSource } from "wj-config"; +import type { IDataSource } from "wj-config"; import { DataSource } from "./DataSource.js"; -import { isConfig } from "./helpers.js"; +import { isConfigNode } 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/SingleValueDataSource.ts index 89ca2e3..dc9f75c 100644 --- a/src/SingleValueDataSource.ts +++ b/src/SingleValueDataSource.ts @@ -1,12 +1,12 @@ -import type { ConfigurationValue, ICoreConfig } from "wj-config"; +import type { ConfigurationValue, Dictionary } from "wj-config"; 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 default 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/helpers.ts b/src/helpers.ts index 04ccbe0..3dd9317 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"; /** * Tests the provided object to determine if it is an array. @@ -15,8 +15,33 @@ 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' + && !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 +104,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/makeWsUrlFunctions.ts b/src/makeWsUrlFunctions.ts index 93545b2..6802a0c 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 type { ConfigurationNode, QueryStringArg, RouteReplacementArg, RouteValuesFn, UrlNode, UrlRoot } from "wj-config"; +import { forEachProperty, isArray, isConfigNode, isFunction } from "./helpers.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..dcf48f7 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,16 @@ "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": "./wj-config.d.ts", + "import": "./index.js", + "default": "./index.js" + } + }, "scripts": { "build": "npx tsc" }, diff --git a/src/wj-config.d.ts b/src/wj-config.d.ts index 790e00f..971c666 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -1,422 +1,408 @@ -declare module 'wj-config' { +/** + * Possible values in a configuration object. + */ +export type SingleConfigurationValue = string | number | Date | boolean | undefined | null; + +export type ConfigurationValue = SingleConfigurationValue | SingleConfigurationValue[]; + +/** + * A configuration node. + */ +export interface ConfigurationNode { + [x: string]: ConfigurationValue | ConfigurationNode +} + +/** + * 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; +} + +export interface IJsonParser> { + parse(json: string): T; +} + +/** + * Defines the capabilities required from data source information objects used in value tracing. + */ +export interface IDataSourceInfo { /** - * WJ-Config module's entry point. Creates a builder object that is used to specify the various configuration - * sources and settings. + * Provides the name of the data source instance that can be used in messages and logs. */ - export default function wjConfig(): IBuilder; + name: string; /** - * Predicate function that evaluates an arbitrary number of criteria against the data and returns a judgment in terms - * of a Boolean value. + * Index (position) of the data source object in the builder's list of data sources. */ - export type Predicate = (data: T) => boolean; + index?: number; +} +/** + * Defines the capabilities required from data sources. + */ +export interface IDataSource = Record> extends IDataSourceInfo { /** - * Possible values in a configuration object. + * Asynchronously obtains the object that will be used as building block in the creation of the final + * configuration object. */ - export type ConfigurationValue = string | number | Date | boolean | IEnvironment | IDefaultEnvironment | undefined | null | Function | ICoreConfig | IDataSourceInfo | IDataSourceInfo[]; + getObject(): Promise; /** - * Type alias that describes the data type of interim or final configuration objects. + * Returns a data source information object on demand. This is used when building a configuration object with + * value tracing. */ - export type IConfig = ICoreConfig | IEnvConfig | IDefaultEnvConfig; + trace(): IDataSourceInfo; +} + +export interface Trace { + [x: string]: IDataSourceInfo | Trace; +} - export type ProcessFetchResponse = (response: Response) => Promise; +/** + * Defines the capabilities required from configuration builders. + */ +export interface IBuilder = {}, TEnvironments extends string | undefined = undefined> { /** - * Defines the core and most basic capabilities found in configuration objects. + * 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 ICoreConfig { - [x: string | symbol]: ConfigurationValue; - } - + add>(dataSource: IDataSource): IBuilder & NewT, TEnvironments>; /** - * Defines the capabilities of a configurtion object that contains an environment object in the environment property. + * 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 IEnvConfig extends ICoreConfig { - environment: IEnvironment; - } - + addObject>(obj: NewT | (() => Promise)): IBuilder & NewT, TEnvironments>; /** - * 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 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 IDefaultEnvConfig extends ICoreConfig { - environment: IDefaultEnvironment - } - - export interface IJsonParser { - parse(json: string): ICoreConfig - } - + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IBuilder & NewT, TEnvironments>; /** - * 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 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 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>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IBuilder & NewT, TEnvironments>; /** - * Defines the capabilities required from data sources. + * 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_. */ - 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; - } - + addEnvironment>(env: Record | (() => Promise>), prefix?: string): IBuilder & NewT, TEnvironments>; /** - * Defines the capabilities required from configuration builders. + * 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 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; - } - + addFetched>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder & NewT, TEnvironments>; /** - * 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 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 type EnvironmentTest = () => boolean; - + addFetched>(request: RequestInfo | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder & NewT, TEnvironments>; /** - * Defines the capabilities required from environment objects. + * 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 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) - } - + addJson>(json: string | (() => Promise), jsonParser?: IJsonParser, reviver?: (this: any, key: string, value: any) => any): IBuilder & NewT, TEnvironments>; /** - * Environment interface that describes the environment object created with default environment names. + * 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 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 - } - + addSingleValue>(path: string, value?: ConfigurationValue, hierarchySeparator?: string): IBuilder & NewT, TEnvironments>; /** - * 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 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 RouteValuesFunction = (name: string) => string - + addSingleValue>(dataFn: () => Promise<[string, ConfigurationValue]>, hierarchySeparator?: string): IBuilder & NewT, TEnvironments>; /** - * Type that describes the possible ways of passing route values when calling a URL-building function. + * 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. */ - export type RouteValues = RouteValuesFunction | { [x: string]: string } - + addPerEnvironment>(addDs: (builder: IBuilder, envName: TEnvironments) => boolean | string): IBuilder & NewT, TEnvironments>; /** - * Type that describes the possible ways of specifing a query string when calling a URL-building function. + * Sets the data source name of the last data source added to the builder. + * @param name Name for the data source. */ - export type QueryString = (() => string | ICoreConfig) | string | ICoreConfig + 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> | undefined>, 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: Exclude, 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): IBuilder & IncludeEnvironment, TEnvironments>; + /** + * 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: Exclude, envNames?: TEnvironments[], propertyName?: TEnvironmentKey): IBuilder & IncludeEnvironment, TEnvironments>; + /** + * 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): IBuilder & 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. + * The current environment (represented by an environment definition). */ - export interface IWsPath extends ICoreConfig { - rootPath?: string; - _rootPath: () => string; - buildUrl: (url: string, replaceValues?: RouteValues, queryString?: QueryString) => string; - } + readonly current: IEnvironmentDefinition>; /** - * 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 list of known environments (represented by a list of environment definitions). */ - export interface IWsParent extends IWsPath { - host?: string; - port?: number; - scheme?: string; - } + readonly all: TEnvironments[]; /** - * Environment class used to provide environment information through the configuration 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 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[]); - } + hasTraits(traits: Traits): boolean; /** - * Environment definition class used to specify the current environment as an object. + * 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 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); - } + hasAnyTrait(traits: Traits): boolean; +} + +export type HasHost = { + host: string; +} +export type HasRootPath = { + rootPath: string; +} + +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; + +export type BuildUrlFn = (path: string, routeValues?: RouteReplacementArg, queryString?: QueryStringArg) => string; + +export type UrlNode = Partial & { + buildUrl: BuildUrlFn; + _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; + +/** + * 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. + */ +export declare function buildEnvironment( + currentEnvironment: string | IEnvironmentDefinition, + possibleEnvironments?: TEnvironments[] +): IEnvironment; + +/** + * Environment definition class used to specify the current environment as an object. + */ +export class EnvironmentDefinition { + public readonly name: string; + public readonly traits: Traits; /** - * Type that defines the acceptable trait types for a single trait. It is encouraged to use number-based 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. */ - export type Trait = number | string; + constructor(name: string, traits?: Traits); +} + +/** + * 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 { /** - * Type that defines the acceptable trait types for multiple traits. It is encouraged to use number-based traits. + * Gets the environment's name. */ - export type Traits = number | string[]; + readonly name: TEnvironments, /** - * Defines the capabilities required from objects used as environment definitions. + * Gets the environment's traits. */ - export interface IEnvironmentDefinition { - /** - * Gets the environment's name. - */ - readonly name: string, - - /** - * Gets the environment's traits. - */ - readonly traits: Traits - } + readonly traits: Traits } diff --git a/tsconfig.json b/tsconfig.json index 0436aae..60abfee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -90,7 +90,7 @@ // "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" From d04cdc1207d0723f59f0d95b5cc9cf9d634d9ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 00:57:21 -0600 Subject: [PATCH 02/15] feat: TypeScript re-write complete --- src/Builder.ts | 378 +++++++++++++++++++++++++++------------------ src/wj-config.d.ts | 177 ++++++++++++++++----- 2 files changed, 367 insertions(+), 188 deletions(-) diff --git a/src/Builder.ts b/src/Builder.ts index b3b872d..0aef639 100644 --- a/src/Builder.ts +++ b/src/Builder.ts @@ -1,4 +1,4 @@ -import type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "wj-config"; +import type { ConfigurationValue, IBuilder, IDataSource, IEnvAwareBuilder, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "wj-config"; import DictionaryDataSource from "./DictionaryDataSource.js"; import { buildEnvironment } from "./Environment.js"; import EnvironmentDataSource from "./EnvironmentDataSource.js"; @@ -10,9 +10,9 @@ import merge from "./Merge.js"; import { ObjectDataSource } from "./ObjectDataSource.js"; import SingleValueDataSource from "./SingleValueDataSource.js"; -interface IEnvironmentSource { +interface IEnvironmentSource { name?: string, - environment: IEnvironment>; + environment: IEnvironment; } interface IUrlData { @@ -20,98 +20,40 @@ interface IUrlData { routeValuesRegExp: RegExp; } -interface IDataSourceDef { +interface IDataSourceDef { dataSource: IDataSource>, - predicate?: Predicate> | undefined> + predicate?: (env?: any) => boolean; } -export default class Builder= {}, TEnvironments extends string | undefined = undefined> implements IBuilder { +class BuilderImpl { /** * 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: Record, number> | null = null; - + _dsDefs: IDataSourceDef[] = []; /** * URL data used to create URL functions out of specific property values in the resulting configuration object. */ - private _urlData?: IUrlData; - + _urlData?: IUrlData; /** * Flag to determine if the last call in the builder was the addition of a data source. */ - private _lastCallWasDsAdd: boolean = false; + _lastCallWasDsAdd: boolean = false; - add>(dataSource: IDataSource) { + add(dataSource: IDataSource>) { this._dsDefs.push({ dataSource: dataSource }); dataSource.index = this._dsDefs.length - 1; this._lastCallWasDsAdd = true; - return this as unknown as IBuilder & NewT, TEnvironments>; - } - - addObject>(obj: NewT | (() => Promise)) { - return this.add(new ObjectDataSource(obj)); - } - - addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); - } - - addEnvironment>(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: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { - return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); - } - - addPerEnvironment>(addDs: (builder: IBuilder, 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 IBuilder & NewT, TEnvironments>; } - 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; - return this as unknown as IBuilder; } - when(predicate: Predicate> | undefined>, dataSourceName?: string) { + 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.'); } @@ -123,58 +65,11 @@ export default class Builder= {}, TEnvironments ex if (dataSourceName != undefined) { this.name(dataSourceName); } - return this as unknown as IBuilder; - } - - 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); - } - - 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); - } - - includeEnvironment( - valueOrEnv: Exclude | IEnvironment>, - propNameOrEnvNames?: Exclude[] | TEnvironmentKey, propertyName?: TEnvironmentKey - ) { - this._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); - } - this._envSource = { - name: propName, - environment: env - }; - return this as unknown as IBuilder & IncludeEnvironment, TEnvironments>; } - createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { + createUrlFunctions(wsPropertyNames: string | number | symbol | (string | number | symbol)[], routeValuesRegExp?: RegExp) { this._lastCallWasDsAdd = false; - let propNames: TUrl[]; + let propNames: (string | number | symbol)[]; if (typeof wsPropertyNames === 'string') { if (wsPropertyNames !== '') { propNames = [wsPropertyNames]; @@ -190,40 +85,17 @@ export default class Builder= {}, TEnvironments ex wsPropertyNames: propNames! as string[], routeValuesRegExp: routeValuesRegExp ?? /\{(\w+)\}/g }; - return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; } - async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { + async build(traceValueSources: boolean = false, evaluatePredicate: Predicate) { 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!.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 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 || ds.predicate(this._envSource?.environment)) { + if (!ds.predicate || evaluatePredicate(ds.predicate)) { qualifyingDs.push(ds.dataSource); } }); @@ -242,13 +114,6 @@ export default class Builder= {}, TEnvironments ex 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) => { @@ -269,7 +134,218 @@ export default class Builder= {}, TEnvironments ex wjConfig._qualifiedDs = []; } } - return wjConfig as T; + return wjConfig; + } +} + +export default class Builder = {}> implements IBuilder { + #controlData: BuilderImpl = new BuilderImpl(); + add>(dataSource: IDataSource) { + this.#controlData.add(dataSource); + return this as unknown as IBuilder & NewT>; + } + + addObject>(obj: NewT | (() => Promise)) { + return this.add(new ObjectDataSource(obj)); + } + + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + } + + addEnvironment>(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: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { + return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); + } + + name(name: string) { + this.#controlData.name(name); + return this; + } + + when(predicate: () => boolean, dataSourceName?: string) { + this.#controlData.when(predicate, dataSourceName); + return this; + } + + includeEnvironment( + valueOrEnv: TEnvironments | IEnvironment, + propNameOrEnvNames?: TEnvironments[] | TEnvironmentKey, + propertyName?: TEnvironmentKey + ) { + this.#controlData._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.#controlData); + } + + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { + this.#controlData.createUrlFunctions(wsPropertyNames, routeValuesRegExp); + return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; + } + + async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { + return this.#controlData.build(traceValueSources, p => p()) as unknown as T; } }; +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 & NewT>; + } + + addObject>(obj: NewT | (() => Promise)) { + return this.add(new ObjectDataSource(obj)); + } + + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + } + + addEnvironment>(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: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { + 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 & NewT>; + } + + 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/wj-config.d.ts b/src/wj-config.d.ts index 971c666..f8cc564 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -81,24 +81,141 @@ export interface Trace { [x: string]: IDataSourceInfo | Trace; } - /** * Defines the capabilities required from configuration builders. */ -export interface IBuilder = {}, TEnvironments extends string | undefined = undefined> { +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 & NewT>; + /** + * 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)): IBuilder & NewT>; + /** + * 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: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IBuilder & NewT>; + /** + * 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: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IBuilder & NewT>; + /** + * 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: Record | (() => Promise>), prefix?: string): IBuilder & NewT>; + /** + * 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): IBuilder & NewT>; + /** + * 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): IBuilder & NewT>; + /** + * 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): IBuilder & NewT>; + /** + * 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 & NewT>; + /** + * 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 & NewT>; + /** + * 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>; + /** + * 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): IBuilder & UrlBuilderSectionWithCheck>; + /** + * Asynchronously builds the final configuration object. + */ + build(traceValueSources?: boolean, enforcePerEnvironmentCoverage?: boolean): Promise; +} + +export interface IEnvAwareBuilder = {}> { /** * 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 & NewT, TEnvironments>; + add>(dataSource: IDataSource): IEnvAwareBuilder & NewT>; /** * 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)): IBuilder & NewT, TEnvironments>; + addObject>(obj: NewT | (() => Promise)): IEnvAwareBuilder & NewT>; /** * Adds the specified dictionary to the collection of data sources that will be used to build the configuration * object. @@ -109,7 +226,7 @@ export interface IBuilder = {}, TEnvironments exte * 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: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IBuilder & NewT, TEnvironments>; + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IEnvAwareBuilder & NewT>; /** * Adds the specified dictionary to the collection of data sources that will be used to build the configuration * object. @@ -119,7 +236,7 @@ export interface IBuilder = {}, TEnvironments exte * @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: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IBuilder & NewT, TEnvironments>; + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IEnvAwareBuilder & NewT>; /** * Adds the qualifying environment variables to the collection of data sources that will be used to build the * configuration object. @@ -129,7 +246,7 @@ export interface IBuilder = {}, TEnvironments exte * 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: Record | (() => Promise>), prefix?: string): IBuilder & NewT, TEnvironments>; + addEnvironment>(env: Record | (() => Promise>), prefix?: string): IEnvAwareBuilder & NewT>; /** * Adds a fetch operation to the collection of data sources that will be used to build the configuration object. * @param url URL to fetch. @@ -137,7 +254,7 @@ export interface IBuilder = {}, TEnvironments exte * @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): IBuilder & NewT, TEnvironments>; + addFetched>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder & NewT>; /** * Adds a fetch operation to the collection of data sources that will be used to build the configuration * object. @@ -146,7 +263,7 @@ export interface IBuilder = {}, TEnvironments exte * @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): IBuilder & NewT, TEnvironments>; + addFetched>(request: RequestInfo | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder & NewT>; /** * Adds the specified JSON string to the collection of data sources that will be used to build the * configuration object. @@ -154,20 +271,25 @@ export interface IBuilder = {}, TEnvironments exte * @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): IBuilder & NewT, TEnvironments>; + addJson>(json: string | (() => Promise), jsonParser?: IJsonParser, reviver?: (this: any, key: string, value: any) => any): IEnvAwareBuilder & NewT>; /** * 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 & NewT, TEnvironments>; + addSingleValue>(path: string, value?: ConfigurationValue, hierarchySeparator?: string): IEnvAwareBuilder & NewT>; /** * 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 & NewT, TEnvironments>; + addSingleValue>(dataFn: () => Promise<[string, ConfigurationValue]>, hierarchySeparator?: string): IEnvAwareBuilder & NewT>; + /** + * 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. * @@ -177,12 +299,7 @@ export interface IBuilder = {}, TEnvironments exte * @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: TEnvironments) => boolean | string): IBuilder & NewT, TEnvironments>; - /** - * 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; + addPerEnvironment>(addDs: (builder: IEnvAwareBuilder, envName: TEnvironments) => boolean | string): IEnvAwareBuilder & NewT>; /** * Makes the last-added data source conditionally inclusive. * @param predicate Predicate function that is run whenever the build function runs. If the predicate returns @@ -190,7 +307,7 @@ export interface IBuilder = {}, TEnvironments exte * @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): IBuilder; + 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. @@ -198,7 +315,7 @@ export interface IBuilder = {}, TEnvironments exte * @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; + 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. @@ -207,7 +324,7 @@ export interface IBuilder = {}, TEnvironments exte * @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; + 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. @@ -215,21 +332,7 @@ export interface IBuilder = {}, TEnvironments exte * @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: Exclude, 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): IBuilder & IncludeEnvironment, TEnvironments>; - /** - * 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: Exclude, envNames?: TEnvironments[], propertyName?: TEnvironmentKey): IBuilder & IncludeEnvironment, TEnvironments>; + 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 @@ -239,7 +342,7 @@ export interface IBuilder = {}, TEnvironments exte * provided, then the default regular expression will match route values of the form {}, such as * {code} or {id}. */ - createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValueRegExp?: RegExp): IBuilder & UrlBuilderSectionWithCheck>; + createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValueRegExp?: RegExp): IEnvAwareBuilder & UrlBuilderSectionWithCheck>; /** * Asynchronously builds the final configuration object. */ From c617857bccac494ecc832e4b3146ed67648edfdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 11:28:46 -0600 Subject: [PATCH 03/15] refactor: Separate classes into individual files --- src/Builder.ts | 292 ++--------------------------------------- src/BuilderImpl.ts | 127 ++++++++++++++++++ src/EnvAwareBuilder.ts | 152 +++++++++++++++++++++ 3 files changed, 291 insertions(+), 280 deletions(-) create mode 100644 src/BuilderImpl.ts create mode 100644 src/EnvAwareBuilder.ts diff --git a/src/Builder.ts b/src/Builder.ts index 0aef639..26050e2 100644 --- a/src/Builder.ts +++ b/src/Builder.ts @@ -1,147 +1,18 @@ -import type { ConfigurationValue, IBuilder, IDataSource, IEnvAwareBuilder, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "wj-config"; +import type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "wj-config"; +import { BuilderImpl } from "./BuilderImpl.js"; import DictionaryDataSource from "./DictionaryDataSource.js"; +import { EnvAwareBuilder, type IEnvironmentSource } from "./EnvAwareBuilder.js"; import { buildEnvironment } from "./Environment.js"; import EnvironmentDataSource from "./EnvironmentDataSource.js"; import FetchedDataSource from "./FetchedDataSource.js"; -import { isConfigNode } 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?: (env?: any) => boolean; -} - -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; - } -} - -export default class Builder = {}> implements IBuilder { - #controlData: BuilderImpl = new BuilderImpl(); +export class Builder = {}> implements IBuilder { + #impl: BuilderImpl = new BuilderImpl(); add>(dataSource: IDataSource) { - this.#controlData.add(dataSource); + this.#impl.add(dataSource); return this as unknown as IBuilder & NewT>; } @@ -170,12 +41,12 @@ export default class Builder = {}> implements IBui } name(name: string) { - this.#controlData.name(name); + this.#impl.name(name); return this; } when(predicate: () => boolean, dataSourceName?: string) { - this.#controlData.when(predicate, dataSourceName); + this.#impl.when(predicate, dataSourceName); return this; } @@ -184,7 +55,7 @@ export default class Builder = {}> implements IBui propNameOrEnvNames?: TEnvironments[] | TEnvironmentKey, propertyName?: TEnvironmentKey ) { - this.#controlData._lastCallWasDsAdd = false; + this.#impl._lastCallWasDsAdd = false; const propName = (typeof propNameOrEnvNames === 'string' ? propNameOrEnvNames : propertyName) ?? 'environment'; const envNames = (propNameOrEnvNames && typeof propNameOrEnvNames !== 'string') ? propNameOrEnvNames : undefined; let env: IEnvironment; @@ -198,154 +69,15 @@ export default class Builder = {}> implements IBui name: propName, environment: env }; - return new EnvAwareBuilder & IncludeEnvironment>(_envSource, this.#controlData); + return new EnvAwareBuilder & IncludeEnvironment>(_envSource, this.#impl); } createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { - this.#controlData.createUrlFunctions(wsPropertyNames, routeValuesRegExp); + this.#impl.createUrlFunctions(wsPropertyNames, routeValuesRegExp); return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; } async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { - return this.#controlData.build(traceValueSources, p => p()) as unknown as T; + return this.#impl.build(traceValueSources, p => p()) as unknown as T; } }; - -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 & NewT>; - } - - addObject>(obj: NewT | (() => Promise)) { - return this.add(new ObjectDataSource(obj)); - } - - addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); - } - - addEnvironment>(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: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { - 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 & NewT>; - } - - 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/BuilderImpl.ts b/src/BuilderImpl.ts new file mode 100644 index 0000000..d341aa6 --- /dev/null +++ b/src/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"; + +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/EnvAwareBuilder.ts b/src/EnvAwareBuilder.ts new file mode 100644 index 0000000..be45e8b --- /dev/null +++ b/src/EnvAwareBuilder.ts @@ -0,0 +1,152 @@ +import { BuilderImpl } from "./BuilderImpl.js"; +import DictionaryDataSource from "./DictionaryDataSource.js"; +import EnvironmentDataSource from "./EnvironmentDataSource.js"; +import FetchedDataSource from "./FetchedDataSource.js"; +import JsonDataSource from "./JsonDataSource.js"; +import { ObjectDataSource } from "./ObjectDataSource.js"; +import SingleValueDataSource from "./SingleValueDataSource.js"; +import type { ConfigurationValue, IDataSource, IEnvAwareBuilder, IEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "./wj-config"; + +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 & NewT>; + } + + addObject>(obj: NewT | (() => Promise)) { + return this.add(new ObjectDataSource(obj)); + } + + addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + } + + addEnvironment>(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: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { + 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 & NewT>; + } + + 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 From 8e221cf215fbb3fe2527877a6d061a220dbce877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 11:29:04 -0600 Subject: [PATCH 04/15] fix: Ensure object is not null in isConfigNode --- src/helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers.ts b/src/helpers.ts index 3dd9317..c7e15a7 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -17,6 +17,7 @@ export function isArray(obj: unknown): obj is any[] { return Array.isArray(obj); */ export function isConfigNode(obj: unknown): obj is ConfigurationNode { return typeof obj === 'object' + && obj !== null && !isArray(obj) && !(obj instanceof Date) && !(obj instanceof Set) From cfe345c1ac8e26db46ac113f0fa00c092dbcabc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 11:29:31 -0600 Subject: [PATCH 05/15] refactor: Re-import Builder --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9c84e5a..6bbd72c 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 "./Builder.js"; +export * from "./DataSource.js"; export * from "./Environment.js"; export * from "./EnvironmentDefinition.js"; -export * from "./DataSource.js"; export default function wjConfig(): IBuilder { return new Builder(); } From 1c03c3c16da87f543a46d6856b71c2cecdc7356b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 11:33:00 -0600 Subject: [PATCH 06/15] fix(DictionaryDataSource): Ensure falsy values are validated --- src/DictionaryDataSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DictionaryDataSource.ts b/src/DictionaryDataSource.ts index 4a1ec68..b7a72e3 100644 --- a/src/DictionaryDataSource.ts +++ b/src/DictionaryDataSource.ts @@ -102,7 +102,7 @@ export default class DictionaryDataSource> extends throw new Error('The provided prefix value cannot be an empty string.'); } this.#prefixOrPredicate = prefixOrPredicate; - if (dictionary && typeof dictionary !== 'function') { + if (typeof dictionary !== 'function') { this.#validateDictionary(dictionary); } this.#dictionary = dictionary; From 04aa4cc5dce89c609f1285db26d828ce424c300b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 11:33:36 -0600 Subject: [PATCH 07/15] tests: Ammend unit tests for new structure --- tests/DictionaryDataSource.test.js | 26 ++++++++++++------- ...nment.test.js => buildEnvironment.test.js} | 26 +++++++++---------- tests/helpers.test.js | 7 +++-- tests/index.test.js | 6 ++--- tests/makeWsUrlFunctions.test.js | 4 +-- 5 files changed, 37 insertions(+), 32 deletions(-) rename tests/{Environment.test.js => buildEnvironment.test.js} (89%) diff --git a/tests/DictionaryDataSource.test.js b/tests/DictionaryDataSource.test.js index 24ef3c7..74fdd18 100644 --- a/tests/DictionaryDataSource.test.js +++ b/tests/DictionaryDataSource.test.js @@ -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: '', @@ -130,20 +130,26 @@ describe('DictionaryDataSource', () => { it(`Should throw an error if constructed with ${t.text} for ${t.target}.`, () => failedConstructionTest(t.dic, t.sep)); }); 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..e0bfc7c 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/Environment.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..5ea5c87 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -1,11 +1,11 @@ import 'chai/register-expect.js'; -import Builder from '../out/Builder.js'; +import { Builder } from '../out/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. 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); } From 7cf4229b333ca5d454a3f6c92e7aece776b90df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 12:17:12 -0600 Subject: [PATCH 08/15] feat(DictionaryDataSource): Validate prefix argument --- src/DictionaryDataSource.ts | 12 +++- tests/DictionaryDataSource.test.js | 104 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/DictionaryDataSource.ts b/src/DictionaryDataSource.ts index b7a72e3..ec1ebc9 100644 --- a/src/DictionaryDataSource.ts +++ b/src/DictionaryDataSource.ts @@ -98,8 +98,16 @@ export default class DictionaryDataSource> extends 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.'); + 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') { diff --git a/tests/DictionaryDataSource.test.js b/tests/DictionaryDataSource.test.js index 74fdd18..2f99897 100644 --- a/tests/DictionaryDataSource.test.js +++ b/tests/DictionaryDataSource.test.js @@ -129,6 +129,110 @@ 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)); + }); describe('getObject', () => { it('Should throw an error if the provided dictionary function returns a non-flat object.', async () => { // Arrange. From 197a8dd42a070b6ee586085061fc62cbd9cec136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 12:17:37 -0600 Subject: [PATCH 09/15] chore(tests): Update packages --- tests/package-lock.json | 1131 +++++++-------------------------------- tests/package.json | 3 +- 2 files changed, 209 insertions(+), 925 deletions(-) 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" From 03bbafa4c866bae85b18bed8df2075e1c49df795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 12:50:43 -0600 Subject: [PATCH 10/15] tests(DictionaryDataSource): Add tests for prefixes --- tests/DictionaryDataSource.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/DictionaryDataSource.test.js b/tests/DictionaryDataSource.test.js index 2f99897..d03ea10 100644 --- a/tests/DictionaryDataSource.test.js +++ b/tests/DictionaryDataSource.test.js @@ -233,6 +233,27 @@ describe('DictionaryDataSource', () => { 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 function returns a non-flat object.', async () => { // Arrange. From 6a6a3e535548a31fba722e84ef73897698b7d760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 12:51:49 -0600 Subject: [PATCH 11/15] chore: Update packages (root) --- package-lock.json | 172 +++------------------------------------------- 1 file changed, 11 insertions(+), 161 deletions(-) diff --git a/package-lock.json b/package-lock.json index b23626d..3e5a8e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wj-config", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -162,21 +162,21 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "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.11", - "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.11.tgz", - "integrity": "sha512-/kxbd+sD/uEG515N/ZYpC6gYs8h89cQ4UIsAq1y6VT4qlNh8xmiSwcP2xU2MbzXFl8J0l2IdONKFweLfYoqhcA==", + "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.0", + "picocolors": "^1.1.1", "sade": "^1.8.1" }, "bin": { @@ -203,9 +203,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "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": { @@ -223,155 +223,5 @@ "dev": true, "license": "ISC" } - }, - "dependencies": { - "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 - }, - "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" - } - }, - "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 - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "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, - "requires": { - "minimatch": "^5.0.1" - } - }, - "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 - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "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, - "requires": { - "npm-normalize-package-bin": "^2.0.0" - } - }, - "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 - }, - "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, - "requires": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^2.0.0", - "npm-normalize-package-bin": "^2.0.0" - } - }, - "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" - } - }, - "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true - }, - "publint": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.11.tgz", - "integrity": "sha512-/kxbd+sD/uEG515N/ZYpC6gYs8h89cQ4UIsAq1y6VT4qlNh8xmiSwcP2xU2MbzXFl8J0l2IdONKFweLfYoqhcA==", - "dev": true, - "requires": { - "npm-packlist": "^5.1.3", - "picocolors": "^1.1.0", - "sade": "^1.8.1" - } - }, - "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, - "requires": { - "mri": "^1.1.0" - } - }, - "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true - }, - "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 - } } } From 3a429c1352408d7bef355cb568f7ec9dbf89232e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 27 Oct 2024 19:03:38 -0600 Subject: [PATCH 12/15] refactor!: Reorganize and export data source classes BREAKING CHANGE --- src/EnvironmentDefinition.ts | 16 ++++- src/Merge.ts | 2 +- src/{Environment.ts => buildEnvironment.ts} | 8 ++- src/{ => builders}/Builder.ts | 16 ++--- src/{ => builders}/BuilderImpl.ts | 8 +-- src/{ => builders}/EnvAwareBuilder.ts | 14 ++--- src/{ => dataSources}/DataSource.ts | 2 +- src/{ => dataSources}/DictionaryDataSource.ts | 6 +- .../EnvironmentDataSource.ts | 6 +- src/{ => dataSources}/FetchedDataSource.ts | 4 +- src/{ => dataSources}/JsonDataSource.ts | 2 +- src/{ => dataSources}/ObjectDataSource.ts | 4 +- .../SingleValueDataSource.ts | 6 +- src/dataSources/index.ts | 8 +++ src/helpers.ts | 2 +- src/index.ts | 7 +-- src/makeWsUrlFunctions.ts | 4 +- src/package.json | 7 ++- src/wj-config.d.ts | 63 +++++++++++-------- tsconfig.json | 6 +- 20 files changed, 118 insertions(+), 73 deletions(-) rename src/{Environment.ts => buildEnvironment.ts} (91%) rename src/{ => builders}/Builder.ts (87%) rename src/{ => builders}/BuilderImpl.ts (95%) rename src/{ => builders}/EnvAwareBuilder.ts (93%) rename src/{ => dataSources}/DataSource.ts (85%) rename src/{ => dataSources}/DictionaryDataSource.ts (95%) rename src/{ => dataSources}/EnvironmentDataSource.ts (60%) rename src/{ => dataSources}/FetchedDataSource.ts (93%) rename src/{ => dataSources}/JsonDataSource.ts (88%) rename src/{ => dataSources}/ObjectDataSource.ts (91%) rename src/{ => dataSources}/SingleValueDataSource.ts (80%) create mode 100644 src/dataSources/index.ts diff --git a/src/EnvironmentDefinition.ts b/src/EnvironmentDefinition.ts index 346afb8..c417b1c 100644 --- a/src/EnvironmentDefinition.ts +++ b/src/EnvironmentDefinition.ts @@ -1,9 +1,23 @@ -import type { IEnvironmentDefinition, Traits } from "wj-config"; +import type { IEnvironmentDefinition, Traits } from "./wj-config.js"; +/** + * 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; + /** + * 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 0d68ae8..83c4f74 100644 --- a/src/Merge.ts +++ b/src/Merge.ts @@ -1,5 +1,5 @@ -import type { ConfigurationNode, IDataSource, IDataSourceInfo, Trace } from "wj-config"; import { forEachProperty, isArray, isConfigNode } from "./helpers.js"; +import type { ConfigurationNode, IDataSource, Trace } from "./wj-config.js"; type TraceRequest = { diff --git a/src/Environment.ts b/src/buildEnvironment.ts similarity index 91% rename from src/Environment.ts rename to src/buildEnvironment.ts index afa594a..80d3169 100644 --- a/src/Environment.ts +++ b/src/buildEnvironment.ts @@ -1,7 +1,7 @@ -import type { EnvironmentTestFn, IEnvironment, IEnvironmentDefinition, Trait, Traits } from "wj-config"; 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') { @@ -14,6 +14,12 @@ 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 = { diff --git a/src/Builder.ts b/src/builders/Builder.ts similarity index 87% rename from src/Builder.ts rename to src/builders/Builder.ts index 26050e2..ab2e95a 100644 --- a/src/Builder.ts +++ b/src/builders/Builder.ts @@ -1,13 +1,13 @@ -import type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "wj-config"; +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, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "../wj-config.js"; import { BuilderImpl } from "./BuilderImpl.js"; -import DictionaryDataSource from "./DictionaryDataSource.js"; import { EnvAwareBuilder, type IEnvironmentSource } from "./EnvAwareBuilder.js"; -import { buildEnvironment } from "./Environment.js"; -import EnvironmentDataSource from "./EnvironmentDataSource.js"; -import FetchedDataSource from "./FetchedDataSource.js"; -import JsonDataSource from "./JsonDataSource.js"; -import { ObjectDataSource } from "./ObjectDataSource.js"; -import SingleValueDataSource from "./SingleValueDataSource.js"; export class Builder = {}> implements IBuilder { #impl: BuilderImpl = new BuilderImpl(); diff --git a/src/BuilderImpl.ts b/src/builders/BuilderImpl.ts similarity index 95% rename from src/BuilderImpl.ts rename to src/builders/BuilderImpl.ts index d341aa6..90f050c 100644 --- a/src/BuilderImpl.ts +++ b/src/builders/BuilderImpl.ts @@ -1,7 +1,7 @@ -import { isConfigNode } from "./helpers.js"; -import makeWsUrlFunctions from "./makeWsUrlFunctions.js"; -import merge from "./Merge.js"; -import { IDataSource, Predicate } from "./wj-config"; +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[]; diff --git a/src/EnvAwareBuilder.ts b/src/builders/EnvAwareBuilder.ts similarity index 93% rename from src/EnvAwareBuilder.ts rename to src/builders/EnvAwareBuilder.ts index be45e8b..8a2a72d 100644 --- a/src/EnvAwareBuilder.ts +++ b/src/builders/EnvAwareBuilder.ts @@ -1,11 +1,11 @@ +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, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "../wj-config.js"; import { BuilderImpl } from "./BuilderImpl.js"; -import DictionaryDataSource from "./DictionaryDataSource.js"; -import EnvironmentDataSource from "./EnvironmentDataSource.js"; -import FetchedDataSource from "./FetchedDataSource.js"; -import JsonDataSource from "./JsonDataSource.js"; -import { ObjectDataSource } from "./ObjectDataSource.js"; -import SingleValueDataSource from "./SingleValueDataSource.js"; -import type { ConfigurationValue, IDataSource, IEnvAwareBuilder, IEnvironment, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "./wj-config"; export interface IEnvironmentSource { name?: string, 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/DictionaryDataSource.ts b/src/dataSources/DictionaryDataSource.ts similarity index 95% rename from src/DictionaryDataSource.ts rename to src/dataSources/DictionaryDataSource.ts index ec1ebc9..9755c00 100644 --- a/src/DictionaryDataSource.ts +++ b/src/dataSources/DictionaryDataSource.ts @@ -1,6 +1,6 @@ -import type { ConfigurationNode, ConfigurationValue, Dictionary, IDataSource, Predicate } from "wj-config"; +import { attemptParse, forEachProperty, isConfigNode } from "../helpers.js"; +import type { ConfigurationNode, ConfigurationValue, Dictionary, IDataSource, Predicate } from "../wj-config.js"; import { DataSource } from "./DataSource.js"; -import { attemptParse, forEachProperty, isConfigNode } from "./helpers.js"; const processKey = (key: string, hierarchySeparator: string, prefix?: string) => { if (prefix) { @@ -16,7 +16,7 @@ const ensurePropertyValue = (obj: ConfigurationNode, name: string) => { return obj[name]; } -export default class DictionaryDataSource> extends DataSource implements IDataSource { +export class DictionaryDataSource> extends DataSource implements IDataSource { #dictionary: Record | (() => Promise>); #hierarchySeparator: string; #prefixOrPredicate?: string | Predicate; diff --git a/src/EnvironmentDataSource.ts b/src/dataSources/EnvironmentDataSource.ts similarity index 60% rename from src/EnvironmentDataSource.ts rename to src/dataSources/EnvironmentDataSource.ts index bb5fe6e..1725735 100644 --- a/src/EnvironmentDataSource.ts +++ b/src/dataSources/EnvironmentDataSource.ts @@ -1,7 +1,7 @@ -import type { Dictionary } from "wj-config"; -import DictionaryDataSource from "./DictionaryDataSource.js"; +import type { Dictionary } from "../wj-config.js"; +import { DictionaryDataSource } from "./DictionaryDataSource.js"; -export default class EnvironmentDataSource> extends DictionaryDataSource { +export class EnvironmentDataSource> extends DictionaryDataSource { constructor(env: Dictionary | (() => Promise), prefix?: string) { super(env, '__', prefix); if (!prefix) { diff --git a/src/FetchedDataSource.ts b/src/dataSources/FetchedDataSource.ts similarity index 93% rename from src/FetchedDataSource.ts rename to src/dataSources/FetchedDataSource.ts index 90b15ff..712d6b0 100644 --- a/src/FetchedDataSource.ts +++ b/src/dataSources/FetchedDataSource.ts @@ -1,7 +1,7 @@ -import type { ConfigurationNode, 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; diff --git a/src/JsonDataSource.ts b/src/dataSources/JsonDataSource.ts similarity index 88% rename from src/JsonDataSource.ts rename to src/dataSources/JsonDataSource.ts index 47a69d4..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; diff --git a/src/ObjectDataSource.ts b/src/dataSources/ObjectDataSource.ts similarity index 91% rename from src/ObjectDataSource.ts rename to src/dataSources/ObjectDataSource.ts index 61002af..499330e 100644 --- a/src/ObjectDataSource.ts +++ b/src/dataSources/ObjectDataSource.ts @@ -1,6 +1,6 @@ -import type { IDataSource } from "wj-config"; +import { isConfigNode } from "../helpers.js"; +import type { IDataSource } from "../wj-config.js"; import { DataSource } from "./DataSource.js"; -import { isConfigNode } from "./helpers.js"; /** * Configuration data source class that injects a pre-build JavaScript object into the configuration build chain. diff --git a/src/SingleValueDataSource.ts b/src/dataSources/SingleValueDataSource.ts similarity index 80% rename from src/SingleValueDataSource.ts rename to src/dataSources/SingleValueDataSource.ts index dc9f75c..6dab2e3 100644 --- a/src/SingleValueDataSource.ts +++ b/src/dataSources/SingleValueDataSource.ts @@ -1,5 +1,5 @@ -import type { ConfigurationValue, Dictionary } from "wj-config"; -import DictionaryDataSource from "./DictionaryDataSource.js"; +import type { ConfigurationValue, Dictionary } from "../wj-config.js"; +import { DictionaryDataSource } from "./DictionaryDataSource.js"; function buildDictionary(key: string | (() => Promise<[string, ConfigurationValue]>), value?: ConfigurationValue) { if (!key) { @@ -19,7 +19,7 @@ function buildDictionary(key: string | (() => Promise<[string, ConfigurationValu return dicFn(key, value); } -export default class SingleValueDataSource> extends DictionaryDataSource { +export class SingleValueDataSource> extends DictionaryDataSource { constructor( path: string | (() => Promise<[string, ConfigurationValue]>), value?: ConfigurationValue, 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 c7e15a7..baaee7e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,6 +1,6 @@ 'use strict'; -import { ConfigurationNode, Dictionary } from "wj-config"; +import { ConfigurationNode, Dictionary } from "./wj-config.js"; /** * Tests the provided object to determine if it is an array. diff --git a/src/index.ts b/src/index.ts index 6bbd72c..122e0b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,7 @@ -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 "./DataSource.js"; -export * from "./Environment.js"; +export * from "./buildEnvironment.js"; export * from "./EnvironmentDefinition.js"; export default function wjConfig(): IBuilder { return new Builder(); diff --git a/src/makeWsUrlFunctions.ts b/src/makeWsUrlFunctions.ts index 6802a0c..0c66815 100644 --- a/src/makeWsUrlFunctions.ts +++ b/src/makeWsUrlFunctions.ts @@ -1,5 +1,5 @@ -import type { ConfigurationNode, QueryStringArg, RouteReplacementArg, RouteValuesFn, UrlNode, UrlRoot } from "wj-config"; -import { forEachProperty, isArray, isConfigNode, isFunction } 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) => ''; diff --git a/src/package.json b/src/package.json index dcf48f7..32a420e 100644 --- a/src/package.json +++ b/src/package.json @@ -41,9 +41,14 @@ "types": "wj-config.d.ts", "exports": { ".": { - "types": "./wj-config.d.ts", + "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": { diff --git a/src/wj-config.d.ts b/src/wj-config.d.ts index f8cc564..9acb14d 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -1,8 +1,11 @@ /** - * Possible values in a configuration object. + * 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[]; /** @@ -41,7 +44,14 @@ export type IncludeEnvironment; } +/** + * Defines the requirements of objects that wish to provide JSON-parsing services. + */ export interface IJsonParser> { + /** + * Parses the provided JSON string data and returns a JavaScript object. + * @param json The JSON string to parse. + */ parse(json: string): T; } @@ -77,6 +87,9 @@ export interface IDataSource = Record trace(): IDataSourceInfo; } +/** + * Defines the shape of configuration-tracing objects. + */ export interface Trace { [x: string]: IDataSourceInfo | Trace; } @@ -387,14 +400,23 @@ export type IEnvironment = { 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; @@ -422,10 +444,25 @@ export type QueryStringArg = Record | string | (() => string | Reco */ 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; + /** + * Calculates the accumulated root path for this node. + * @returns The accumulated root path for this node as a string. + */ _rootPath: () => string; } & { [x: string]: UrlBuilderFn; @@ -460,30 +497,6 @@ export type UrlBuilderSectionWithCheck = T extends HasH } : T; -/** - * 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. - */ -export declare function buildEnvironment( - currentEnvironment: string | IEnvironmentDefinition, - possibleEnvironments?: TEnvironments[] -): IEnvironment; - -/** - * Environment definition class used to specify the current environment as an object. - */ -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); -} - /** * Type that defines the acceptable trait types for a single trait. It is encouraged to use number-based traits. */ diff --git a/tsconfig.json b/tsconfig.json index 60abfee..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. */ @@ -93,6 +93,6 @@ "skipLibCheck": false /* Skip type checking all .d.ts files. */ }, "include": [ - "./src/*.ts" + "./src/**/*.ts" ] } \ No newline at end of file From 2be957a275c71c7b66feed4909919f41722cb7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 3 Nov 2024 09:26:23 -0600 Subject: [PATCH 13/15] feat: Introduce the MergeResult type --- src/buildEnvironment.ts | 5 ++- src/builders/Builder.ts | 12 +++---- src/builders/EnvAwareBuilder.ts | 6 ++-- src/wj-config.d.ts | 59 +++++++++++++++++++-------------- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/buildEnvironment.ts b/src/buildEnvironment.ts index 80d3169..8f1714d 100644 --- a/src/buildEnvironment.ts +++ b/src/buildEnvironment.ts @@ -20,7 +20,10 @@ function capitalize(text: string) { * @param possibleEnvironments The complete list of all possible environments. * @returns The newly created `IEnvironment` object. */ -export function buildEnvironment(currentEnvironment: TEnvironments | IEnvironmentDefinition, possibleEnvironments?: TEnvironments[]): IEnvironment { +export function buildEnvironment( + currentEnvironment: TEnvironments | IEnvironmentDefinition, + possibleEnvironments?: TEnvironments[] +): IEnvironment { const defaultNames: string[] = ['Development', 'PreProduction', 'Production']; const env = { all: possibleEnvironments ?? defaultNames, diff --git a/src/builders/Builder.ts b/src/builders/Builder.ts index ab2e95a..7dac198 100644 --- a/src/builders/Builder.ts +++ b/src/builders/Builder.ts @@ -5,7 +5,7 @@ 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, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "../wj-config.js"; +import type { ConfigurationValue, IBuilder, IDataSource, IEnvironment, IncludeEnvironment, MergeResult, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "../wj-config.js"; import { BuilderImpl } from "./BuilderImpl.js"; import { EnvAwareBuilder, type IEnvironmentSource } from "./EnvAwareBuilder.js"; @@ -13,7 +13,7 @@ export class Builder = {}> implements IBuilder #impl: BuilderImpl = new BuilderImpl(); add>(dataSource: IDataSource) { this.#impl.add(dataSource); - return this as unknown as IBuilder & NewT>; + return this as unknown as IBuilder>; } addObject>(obj: NewT | (() => Promise)) { @@ -65,11 +65,11 @@ export class Builder = {}> implements IBuilder else { env = buildEnvironment(valueOrEnv, envNames); } - const _envSource: IEnvironmentSource = { + const envSource: IEnvironmentSource = { name: propName, environment: env }; - return new EnvAwareBuilder & IncludeEnvironment>(_envSource, this.#impl); + return new EnvAwareBuilder & IncludeEnvironment>(envSource, this.#impl); } createUrlFunctions(wsPropertyNames: TUrl | TUrl[], routeValuesRegExp?: RegExp) { @@ -77,7 +77,7 @@ export class Builder = {}> implements IBuilder return this as unknown as IBuilder & UrlBuilderSectionWithCheck>; } - async build(traceValueSources: boolean = false, enforcePerEnvironmentCoverage: boolean = true) { - return this.#impl.build(traceValueSources, p => p()) as unknown as T; + build(traceValueSources: boolean = false) { + return this.#impl.build(traceValueSources, p => p()) as unknown as Promise; } }; diff --git a/src/builders/EnvAwareBuilder.ts b/src/builders/EnvAwareBuilder.ts index 8a2a72d..2b115c2 100644 --- a/src/builders/EnvAwareBuilder.ts +++ b/src/builders/EnvAwareBuilder.ts @@ -4,7 +4,7 @@ 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, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "../wj-config.js"; +import type { ConfigurationValue, IDataSource, IEnvAwareBuilder, IEnvironment, MergeResult, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "../wj-config.js"; import { BuilderImpl } from "./BuilderImpl.js"; export interface IEnvironmentSource { @@ -26,7 +26,7 @@ export class EnvAwareBuilder>(dataSource: IDataSource) { this.#impl.add(dataSource); - return this as unknown as IEnvAwareBuilder & NewT>; + return this as unknown as IEnvAwareBuilder>; } addObject>(obj: NewT | (() => Promise)) { @@ -84,7 +84,7 @@ export class EnvAwareBuilder & NewT>; + return this as unknown as IEnvAwareBuilder>; } when(predicate: Predicate | undefined>, dataSourceName?: string) { diff --git a/src/wj-config.d.ts b/src/wj-config.d.ts index 9acb14d..2181e5d 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -15,6 +15,18 @@ export interface ConfigurationNode { [x: string]: ConfigurationValue | ConfigurationNode } +/** + * Types an object-merging operation's result. + */ +export type MergeResult, NewT extends Record> = (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; + /** * Defines the shape of dictionaries. */ @@ -103,14 +115,14 @@ export interface IBuilder = {}> { * configuration object. * @param dataSource The data source to include. */ - add>(dataSource: IDataSource): IBuilder & NewT>; + 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: NewT | (() => Promise)): IBuilder & NewT>; + addObject>(obj: NewT | (() => Promise)): IBuilder>; /** * Adds the specified dictionary to the collection of data sources that will be used to build the configuration * object. @@ -121,7 +133,7 @@ export interface IBuilder = {}> { * 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: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IBuilder & NewT>; + addDictionary>(dictionary: Record | (() => 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. @@ -131,7 +143,7 @@ export interface IBuilder = {}> { * @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: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IBuilder & NewT>; + addDictionary>(dictionary: Record | (() => 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. @@ -141,7 +153,7 @@ export interface IBuilder = {}> { * 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: Record | (() => Promise>), prefix?: string): IBuilder & NewT>; + addEnvironment>(env: Record | (() => 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. @@ -149,7 +161,7 @@ export interface IBuilder = {}> { * @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): IBuilder & NewT>; + addFetched>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IBuilder>; /** * Adds a fetch operation to the collection of data sources that will be used to build the configuration * object. @@ -158,7 +170,7 @@ export interface IBuilder = {}> { * @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): IBuilder & NewT>; + addFetched>(request: RequestInfo | (() => Promise), required?: boolean, 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. @@ -166,20 +178,20 @@ export interface IBuilder = {}> { * @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): IBuilder & NewT>; + addJson>(json: string | (() => Promise), jsonParser?: IJsonParser, 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 & NewT>; + 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 & NewT>; + addSingleValue>(dataFn: () => Promise<[string, ConfigurationValue]>, hierarchySeparator?: string): IBuilder>; /** * Sets the data source name of the last data source added to the builder. * @param name Name for the data source. @@ -212,7 +224,7 @@ export interface IBuilder = {}> { /** * Asynchronously builds the final configuration object. */ - build(traceValueSources?: boolean, enforcePerEnvironmentCoverage?: boolean): Promise; + build(traceValueSources?: boolean): Promise; } export interface IEnvAwareBuilder = {}> { @@ -221,14 +233,13 @@ export interface IEnvAwareBuilder>(dataSource: IDataSource): IEnvAwareBuilder & NewT>; + add>(dataSource: IDataSource): IEnvAwareBuilder>; /** - * Adds the specified object to the collection of data sources that will be used to build the configuration - * object. + * 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 & NewT>; + addObject>(obj: NewT | (() => Promise)): IEnvAwareBuilder>; /** * Adds the specified dictionary to the collection of data sources that will be used to build the configuration * object. @@ -239,7 +250,7 @@ export interface IEnvAwareBuilder>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IEnvAwareBuilder & NewT>; + addDictionary>(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. @@ -249,7 +260,7 @@ export interface IEnvAwareBuilder>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IEnvAwareBuilder & NewT>; + addDictionary>(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. @@ -259,7 +270,7 @@ export interface IEnvAwareBuilder>(env: Record | (() => Promise>), prefix?: string): IEnvAwareBuilder & NewT>; + addEnvironment>(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. @@ -267,7 +278,7 @@ export interface IEnvAwareBuilder>(url: URL | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder & NewT>; + 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. @@ -276,7 +287,7 @@ export interface IEnvAwareBuilder>(request: RequestInfo | (() => Promise), required?: boolean, init?: RequestInit, processFn?: ProcessFetchResponse): IEnvAwareBuilder & NewT>; + 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. @@ -284,20 +295,20 @@ export interface IEnvAwareBuilder>(json: string | (() => Promise), jsonParser?: IJsonParser, reviver?: (this: any, key: string, value: any) => any): IEnvAwareBuilder & NewT>; + 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 & NewT>; + 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 & NewT>; + 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. @@ -312,7 +323,7 @@ export interface IEnvAwareBuilder>(addDs: (builder: IEnvAwareBuilder, envName: TEnvironments) => boolean | string): IEnvAwareBuilder & NewT>; + 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 From 49bf046299b02126f9ecd4c968c0b4d0aed0ac1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 3 Nov 2024 09:26:49 -0600 Subject: [PATCH 14/15] docs: Update README and publish note --- PublishNote.md | 6 +++- README.md | 78 ++++++++++++++++++++++++++------------------------ 2 files changed, 45 insertions(+), 39 deletions(-) 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**. From 7d02005ec6de5b9444e8368e6a0b129ec508fe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Wed, 6 Nov 2024 19:50:23 -0600 Subject: [PATCH 15/15] feat: Type dictionaries and single values --- src/builders/Builder.ts | 15 ++++++----- src/builders/EnvAwareBuilder.ts | 15 ++++++----- src/index.ts | 1 + src/wj-config.d.ts | 41 ++++++++++++++++++++++-------- tests/DataSource.test.js | 2 +- tests/DictionaryDataSource.test.js | 2 +- tests/buildEnvironment.test.js | 2 +- tests/index.test.js | 6 +---- 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/builders/Builder.ts b/src/builders/Builder.ts index 7dac198..47fda39 100644 --- a/src/builders/Builder.ts +++ b/src/builders/Builder.ts @@ -5,7 +5,7 @@ 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, MergeResult, Predicate, ProcessFetchResponse, UrlBuilderSectionWithCheck } from "../wj-config.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"; @@ -20,12 +20,13 @@ export class Builder = {}> implements IBuilder return this.add(new ObjectDataSource(obj)); } - addDictionary>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, prefixOrPredicate?: string | Predicate) { + return this.add, unknown>>(new DictionaryDataSource(dictionary, hierarchySeparator ?? ':', prefixOrPredicate)); } - addEnvironment>(env: Record | (() => Promise>), prefix: string = 'OPT_') { - return this.add(new EnvironmentDataSource(env, prefix)); + 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) { @@ -36,8 +37,8 @@ export class Builder = {}> implements IBuilder return this.add(new JsonDataSource(json, jsonParser, reviver)); } - addSingleValue>(path: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { - return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); + 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) { diff --git a/src/builders/EnvAwareBuilder.ts b/src/builders/EnvAwareBuilder.ts index 2b115c2..3de64fb 100644 --- a/src/builders/EnvAwareBuilder.ts +++ b/src/builders/EnvAwareBuilder.ts @@ -4,7 +4,7 @@ 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, MergeResult, Predicate, ProcessFetchResponse, Traits, UrlBuilderSectionWithCheck } from "../wj-config.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 { @@ -33,12 +33,13 @@ export class EnvAwareBuilder>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { - return this.add(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); + addDictionary, TSep extends string = ':'>(dictionary: Record | (() => Promise>), hierarchySeparator: string = ':', prefixOrPredicate?: string | Predicate) { + // @ts-expect-error + return this.add>(new DictionaryDataSource(dictionary, hierarchySeparator, prefixOrPredicate)); } - addEnvironment>(env: Record | (() => Promise>), prefix: string = 'OPT_') { - return this.add(new EnvironmentDataSource(env, prefix)); + 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) { @@ -49,8 +50,8 @@ export class EnvAwareBuilder(new JsonDataSource(json, jsonParser, reviver)); } - addSingleValue>(path: string | (() => Promise<[string, ConfigurationValue]>), valueOrHierarchySeparator?: ConfigurationValue | string, hierarchySeparator?: string) { - return this.add(new SingleValueDataSource(path, valueOrHierarchySeparator, typeof path === 'function' ? valueOrHierarchySeparator as string : hierarchySeparator)); + 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) { diff --git a/src/index.ts b/src/index.ts index 122e0b0..51b040a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { type IBuilder } from "./wj-config.js"; export * from "./buildEnvironment.js"; export * from "./EnvironmentDefinition.js"; +export type * from "./wj-config.js"; export default function wjConfig(): IBuilder { return new Builder(); } diff --git a/src/wj-config.d.ts b/src/wj-config.d.ts index 2181e5d..1dd8a7f 100644 --- a/src/wj-config.d.ts +++ b/src/wj-config.d.ts @@ -18,7 +18,7 @@ export interface ConfigurationNode { /** * Types an object-merging operation's result. */ -export type MergeResult, NewT extends Record> = (Omit & { +export type MergeResult, NewT> = (Omit & { [K in keyof NewT]-?: K extends keyof T ? ( T[K] extends Record ? @@ -26,6 +26,25 @@ export type MergeResult, NewT 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. @@ -133,7 +152,7 @@ export interface IBuilder = {}> { * 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: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IBuilder>; + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, prefix?: string): IBuilder>>; /** * Adds the specified dictionary to the collection of data sources that will be used to build the configuration * object. @@ -143,7 +162,7 @@ export interface IBuilder = {}> { * @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: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IBuilder>; + addDictionary, TSep extends string = ':'>(dictionary: TDic | (() => Promise), hierarchySeparator?: TSep, predicate?: Predicate): IBuilder>>; /** * Adds the qualifying environment variables to the collection of data sources that will be used to build the * configuration object. @@ -153,7 +172,7 @@ export interface IBuilder = {}> { * 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: Record | (() => Promise>), prefix?: string): IBuilder>; + addEnvironment, TPrefix extends string = 'OPT_'>(env: TDic | (() => Promise), prefix?: TPrefix): 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. @@ -185,13 +204,13 @@ export interface IBuilder = {}> { * @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>; + addSingleValue(path: TKey, value?: TValue, hierarchySeparator?: TSep): 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>; + 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. @@ -250,7 +269,7 @@ export interface IEnvAwareBuilder>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, prefix?: string): IEnvAwareBuilder>; + 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. @@ -260,7 +279,7 @@ export interface IEnvAwareBuilder>(dictionary: Record | (() => Promise>), hierarchySeparator?: string, predicate?: Predicate): IEnvAwareBuilder>; + 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. @@ -270,7 +289,7 @@ export interface IEnvAwareBuilder>(env: Record | (() => Promise>), prefix?: string): IEnvAwareBuilder>; + 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. @@ -302,13 +321,13 @@ export interface IEnvAwareBuilder>(path: string, value?: ConfigurationValue, hierarchySeparator?: string): IEnvAwareBuilder>; + 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>; + 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. 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 d03ea10..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.', () => { diff --git a/tests/buildEnvironment.test.js b/tests/buildEnvironment.test.js index e0bfc7c..fab7d87 100644 --- a/tests/buildEnvironment.test.js +++ b/tests/buildEnvironment.test.js @@ -1,5 +1,5 @@ import 'chai/register-expect.js'; -import { buildEnvironment } from '../out/Environment.js'; +import { buildEnvironment } from '../out/buildEnvironment.js'; import { forEachProperty, isConfigNode, isFunction } from '../out/helpers.js'; const testEnvNames = [ diff --git a/tests/index.test.js b/tests/index.test.js index 5ea5c87..f09b112 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -1,5 +1,5 @@ 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', () => { @@ -11,10 +11,6 @@ describe('All Exports', () => { // 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;