diff --git a/README.md b/README.md index 37ccf7d..4df590d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @kaminooni/env -Easy yet type-safe access to process.env variables. +Simple, declarative, and type-safe access to process.env variables. ## Usage @@ -12,154 +12,78 @@ enum Environment { Development = 'development' } -const ENVIRONMENT: Environment = env('NODE_ENV') - .required() - .enum(Environment) - -const DEBUG = env('DEBUG') - .default(false) - .boolean() - -const APP_NAME = env('APP_NAME') - .default('My App') - .trim() - .string() - -const APP_SECRET = env('APP_SECRET') - .required() - .string() - -const SERVER_PORT = env('SERVER_PORT') - .default(3000) - .range(0, 65536) - -const API_VERSION = env('API_VERSION') - .permit(['v1', 'v2', 'v3']) - .default('v3') - .string() +const config = { + environment: env('NODE_ENV').enum(Environment), + debug: env('DEBUG', false).boolean(), + port: env('PORT', 3000).range(0, 65536), + app: { + name: env('APP_NAME', 'My App').string(), + secret: env('APP_SECRET').string(), + apiVersion: env('API_VERSION', 'v3').whitelist(['v1', 'v2', 'v3']), + emails: env('EMAILS', ['myemail@example.com']).json(), + retries: env('RETRIES', 5).number(), + }, +} ``` ## env(...) function -> env(key: string) => EnvVariableBuilder - -This function takes Env variable name, reads this variable from `process.env` and returns the instance -of `EnvVariableBuilder` +> env(key: string, defaultValue?: string) => VariableBuilder -## Builder (EnvVariableBuilder) +This function takes Env variable name, reads this variable from `process.env` and returns the instance of `VariableBuilder` -Builder helps you to validate, transform, set default, and cast the env variables to a type of your choice. - -### permit(values: string[]) - -Verifies that value is one of the list. - -- This function does nothing if env value is `undefined` - -```ts -const API_VERSION = env('API_VERSION') - .permit(['v1', 'v2', 'v3']) - .string() -``` - -### range(from: number, to: number) - -Verifies that value is a number in the specified range. - -- Returns number -- Both limits are inclusive - -```ts -const SERVER_PORT = env('SERVER_PORT') - .default(3000) - .range(0, 65536) -``` - -### required() - -Verifies that value is not undefined. -Throws `MissingRequiredVariableError` if env value is `undefined` - -```ts -const APP_SECRET = env('APP_SECRET') - .required() - .string() -``` +## VariableBuilder -### default(value: string | number | boolean) - -Sets the default value if env value is `undefined` - -```ts -const DEBUG = env('DEBUG') - .default(false) - .boolean() -``` - -### trim() - -Trims the env value - -- This function does nothing if env value is `undefined` - -```ts -const APP_NAME = env('APP_NAME') - .trim() - .string() -``` +VariableBuilder helps you to validate, transform, and cast the env variables to a type of your choice. +All values are considered required, unless the default value is provided. If you try to access the non-existent variable, the builder will throw an error. ### string() / toString() Converts env variable to `string` -- `undefined` value will be converted to empty string `''` - ```ts -const APP_NAME = env('APP_NAME') - .string() +env('APP_NAME').string() ``` ### boolean() Converts env variable to `boolean` - -- Case-insensitive -- By default, returns `true` if env value equals `'true'`. Returns `false` otherwise. +Returns `true` if env value equals `'true'` (case-insensitive). Returns `false` otherwise. ```ts -const DEBUG = env('DEBUG').boolean() +env('DEBUG').boolean() ``` -#### How to change the default list of allowed true values +### number() -List of allowed "true" values could be changed by updating the `EnvVariableBuilder.allowedTrueValues` variable +Converts env variable to `number`. +> NOTE: Empty string `''` will be converted to `0`. ```ts -import { EnvVariableBuilder } from '@kaminooni/env' +env('MAX_CONNECTIONS').number() +``` -EnvVariableBuilder.allowedTrueValues = ['1'] +### whitelist(values: string[]) -process.env['DEBUG'] = 1 +Verifies that value is in the white list. Throws an `Error` if it's not. -const DEBUG = env('DEBUG').boolean() // Returns true +```ts +env('API_VERSION').whitelist(['v1', 'v2', 'v3']) ``` -### number() - -Converts env variable to `number` +### range(from: number, to: number) -- `undefined` value will be converted to `NaN` -- Empty string `''` will be converted to `0` +Verifies that value is a number in the specified range. Both limits are inclusive. +Throws an `Error` if value is not in the specified range. ```ts -const MAX_CONNECTIONS = env('MAX_CONNECTIONS').number() +env('SERVER_PORT', 3000).range(0, 65536) ``` ### enum() -Converts variable to `enum` - -- Throws `UnexpectedValueError` if the value exists, and it is not member of provided Enum +Converts variable to the member of specified `enum`. +Throws an `Error` if the value is not a member of the provided Enum. ```ts enum Environment { @@ -167,7 +91,14 @@ enum Environment { Development = 'development' } -const ENVIRONMENT: Environment = env('NODE_ENV') - .required() - .enum(Environment) +const ENVIRONMENT: Environment = env('NODE_ENV').enum(Environment) +``` + +### json() +Converts env variable to JS object using JSON.parse() function + +> NOTE: This functions doesn't check that JSON comply with the specified type. + +```ts +const emails: string[] = env('EMAILS', ['myemail@example.com']).json() ``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9f81f2c..bc85f72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "env", - "version": "1.0.2", + "name": "@kaminooni/env", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "env", - "version": "1.0.2", + "name": "@kaminooni/env", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@types/jest": "^29.2.0", @@ -39,17 +39,89 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.0.tgz", @@ -90,22 +162,23 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.0.tgz", - "integrity": "sha512-GUPcXxWibClgmYJuIwC2Bc2Lg+8b9VjaJ+HlNdACEVt+Wlr1eoU1OPZjZRm7Hzl0gaTsUZNQfeihvZJhG7oc3w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -145,43 +218,43 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -240,30 +313,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -293,13 +366,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -378,9 +451,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.0.tgz", - "integrity": "sha512-G9VgAhEaICnz8iiJeGJQyVl6J2nTjbW0xeisva0PK6XcKsga7BIaqm4ZF8Rg1Wbaqmy6znspNqhPaPkyukujzg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -567,34 +640,34 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.0.tgz", - "integrity": "sha512-5+cAXQNARgjRUK0JWu2UBwja4JLSO/rBMPJzpsKb+oBF5xlUuCfljQepS4XypBQoiigL0VQjTZy6WiONtUdScQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "debug": "^4.1.0", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -611,13 +684,13 @@ } }, "node_modules/@babel/types": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz", - "integrity": "sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2857,9 +2930,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3622,9 +3695,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4218,9 +4291,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4644,9 +4717,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4755,12 +4828,71 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -4793,21 +4925,22 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, "@babel/generator": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.0.tgz", - "integrity": "sha512-GUPcXxWibClgmYJuIwC2Bc2Lg+8b9VjaJ+HlNdACEVt+Wlr1eoU1OPZjZRm7Hzl0gaTsUZNQfeihvZJhG7oc3w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "requires": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -4837,36 +4970,36 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -4910,24 +5043,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -4948,13 +5081,13 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -5017,9 +5150,9 @@ } }, "@babel/parser": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.0.tgz", - "integrity": "sha512-G9VgAhEaICnz8iiJeGJQyVl6J2nTjbW0xeisva0PK6XcKsga7BIaqm4ZF8Rg1Wbaqmy6znspNqhPaPkyukujzg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -5149,31 +5282,31 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.0.tgz", - "integrity": "sha512-5+cAXQNARgjRUK0JWu2UBwja4JLSO/rBMPJzpsKb+oBF5xlUuCfljQepS4XypBQoiigL0VQjTZy6WiONtUdScQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "debug": "^4.1.0", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "dependencies": { @@ -5186,13 +5319,13 @@ } }, "@babel/types": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz", - "integrity": "sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -6857,9 +6990,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -7444,9 +7577,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -7860,9 +7993,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -8148,9 +8281,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index 26928b6..bdb2596 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@kaminooni/env", "description": "Type-save env variables access", - "version": "1.0.2", + "version": "2.0.0", "author": { "name": "kaminooni", "url": "https://github.com/kaminooni" diff --git a/src/EnvVariableBuilder.ts b/src/EnvVariableBuilder.ts deleted file mode 100644 index 0eb74c1..0000000 --- a/src/EnvVariableBuilder.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { MissingRequiredVariableError, UnexpectedValueError } from './errors' - -export class EnvVariableBuilder { - public static allowedTrueValues = ['true'] - - private value?: string - private readonly key: string - - constructor(key: string, value?: string) { - this.key = key - this.value = value - } - - /** - * Verifies that value is not undefined - * - * @throws {MissingRequiredVariableError} if value is undefined - */ - required(): EnvVariableBuilder { - if (this.value === undefined) { - throw new MissingRequiredVariableError(this.key) - } - - return this - } - - /** - * Verifies that value is one of the list. - * - Ignores undefiled values - * - * @param values - * @throws {UnexpectedValueError} if the value exists, and it is not included in the provided list - */ - permit(values: string[]): EnvVariableBuilder { - if (this.value !== undefined && !values.includes(this.value)) { - throw new UnexpectedValueError(this.key, this.value) - } - - return this - } - - /** - * Sets the default value if the value is `undefined` - * - * @param value - */ - default(value: string | number | boolean): EnvVariableBuilder { - this.value ??= value.toString() - - return this - } - - /** - * Trims the value - * - Ignores undefiled values - */ - trim(): EnvVariableBuilder { - if (this.value !== undefined) { - this.value = this.value.trim() - } - - return this - } - - /** - * Converts env variable to `string` - * - `undefined` value will be converted to empty string `''` - */ - string(): string { - return this.value ?? '' - } - - /** - * Converts env variable to `boolean` - */ - boolean(): boolean { - return EnvVariableBuilder.allowedTrueValues.includes( - this.string().toLowerCase() - ) - } - - /** - * Converts env variable to `number` - */ - number(): number { - return Number(this.value) - } - - /** - * Converts variable to enum - * - * @param enumerable - * @throws {UnexpectedValueError} if the value exists, and it is not member of provided Enum - */ - enum>( - enumerable: T - ): T[keyof T] { - const allowedValues = Object.values(enumerable).map((value) => - value.toString() - ) - - return this.permit(allowedValues).string() as T[keyof T] - } - - /** - * Verifies that value is a number in the specified range - * - Both limits are inclusive - * - * @param from Lower limit (inclusive) - * @param to Upper limit (inclusive) - * @throws {UnexpectedValueError} if the value exists and is outside the specified range - */ - range(from: number, to: number): number { - const value = this.number() - - if (value > to || value < from) { - throw new UnexpectedValueError(this.key, this.value) - } - - return value - } - - toString(): string { - return this.string() - } -} diff --git a/src/VariableBuilder.ts b/src/VariableBuilder.ts new file mode 100644 index 0000000..75b67de --- /dev/null +++ b/src/VariableBuilder.ts @@ -0,0 +1,106 @@ +export class VariableBuilder { + constructor( + private readonly name: string, + private readonly value?: string + ) { + } + + /** + * Verifies that value is one of the list. + * + * @param values + * @throws {Error} if the value is not included in the provided list + */ + whitelist(values: string[]): string { + const value = this.string() + + if (!values.includes(value)) { + throw new Error( + `Unexpected value of the env variable: "${this.name}". Value is not in the white list.` + ); + } + + return value; + } + + /** + * Converts env variable to `string` + * + * @throws {Error} if value is undefined + */ + string(): string { + if (this.value === undefined) { + throw new Error(`Missing required env variable: "${this.name}"`); + } + + return this.value; + } + + /** + * Converts env variable to `boolean` + * + * @throws {Error} if value is undefined + * @return {boolean} Returns `true` if env value equals `'true'` (case-insensitive). Returns `false` otherwise. + */ + boolean(): boolean { + return this.string().toLowerCase().trim() === "true"; + } + + /** + * Converts env variable to `number` + * + * @throws {Error} if value is undefined + */ + number(): number { + return Number(this.string()); + } + + /** + * Converts env variable to JS object using JSON.parse function + * + * @throws {Error} if value is undefined + */ + json(): T { + return JSON.parse(this.string()) + } + + /** + * Converts variable to enum + * + * @param enumerable + * @throws {Error} if the value is undefined or not a member of the provided Enum + */ + enum>( + enumerable: T + ): T[keyof T] { + const allowedValues = Object.values(enumerable).map((value) => + value.toString() + ); + + return this.whitelist(allowedValues) as T[keyof T]; + } + + /** + * Verifies that value is a number in the specified range + * - Both limits are inclusive + * + * @param from Lower limit (inclusive) + * @param to Upper limit (inclusive) + * @throws {Error} if the value is undefined or outside the specified range + */ + range(from: number, to: number): number { + const value = this.number(); + + if (value > to || value < from) { + throw new Error( + `Unexpected value of the env variable "${this.name}". Value is not in range: [${from},${to}]` + ); + } + + return value; + } + + toString(): string { + return this.string(); + } +} diff --git a/src/__tests__/EnvVariableBuilder.test.ts b/src/__tests__/EnvVariableBuilder.test.ts deleted file mode 100644 index 6bfd7e8..0000000 --- a/src/__tests__/EnvVariableBuilder.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { EnvVariableBuilder } from '../EnvVariableBuilder' -import { MissingRequiredVariableError, UnexpectedValueError } from '../errors' - -describe('EnvVariableBuilder', () => { - enum TestStringEnum { - ENUM_VALUE = 'e_value', - } - - enum TestNumericEnum { - ENUM_VALUE_1, - ENUM_VALUE_2, - ENUM_VALUE_5 = 5, - } - - it('should read value from environment', () => { - const builder = new EnvVariableBuilder('TEST_KEY', 'TEST_VALUE') - - expect(builder.string()).toEqual('TEST_VALUE') - }) - - it.each([ - ['S1', 'Some String Value', 'Some String Value'], - ['S2', '123', '123'], - ['S3', '', ''], - ['S4', undefined, ''], - ])( - '%s should correctly read string value `%s` == `%s`', - (key: string, value: string | undefined, result: string) => { - const builder = new EnvVariableBuilder(key, value) - - expect(builder.string()).toEqual(result) - expect(builder.toString()).toEqual(result) - } - ) - - it.each([ - ['B1', 'true', true], - ['B2', 'false', false], - ['B3', 'some random string', false], - ['B4', '123', false], - ['B5', '1', false], - ['B6', '0', false], - ['B7', '', false], - ])( - '%s should correctly read boolean value `%s` == `%s`', - (key: string, value: string, result: boolean) => { - const builder = new EnvVariableBuilder(key, value) - - expect(builder.boolean()).toEqual(result) - } - ) - - it.each([ - ['N1', '123', 123], - ['N2', '-123', -123], - ['N3', '0.23', 0.23], - ['N4', '2.99', 2.99], - ['N5', '.65', 0.65], - ['N6', '', 0], - ['N7', 'some random string', NaN], - ['N8', '1a2', NaN], - ['N9', 'a1', NaN], - ['N10', '1a', NaN], - ])( - '%s should correctly read number value `%s` == `%s`', - (key: string, value: string, result: number) => { - const builder = new EnvVariableBuilder(key, value) - - expect(builder.number()).toEqual(result) - } - ) - - it('should correctly read string Enum value', () => { - const key = Object.keys(TestStringEnum)[0] - const value = Object.values(TestStringEnum)[0] - const builder = new EnvVariableBuilder(key, value) - - expect(builder.enum(TestStringEnum)).toEqual(value) - }) - - it("should throw UnexpectedValueError if string Enum value doesn't exist", () => { - const builder = new EnvVariableBuilder('TestStringEnum', 'invalid') - - expect(() => builder.enum(TestStringEnum)).toThrow(UnexpectedValueError) - }) - - it('should correctly read number Enum value', () => { - const value = '5' - const builder = new EnvVariableBuilder('TestNumericEnum1', value) - - expect(builder.enum(TestNumericEnum)).toEqual(value) - }) - - it("should throw UnexpectedValueError if numeric Enum value doesn't exist", () => { - const builder = new EnvVariableBuilder('TestNumericEnum2', '4') - - expect(() => builder.enum(TestNumericEnum)).toThrow(UnexpectedValueError) - }) - - it('should not throw MissingRequiredVariableError if required value exist', () => { - const builder = new EnvVariableBuilder('required', 'variable') - - expect(() => builder.required()).not.toThrow(MissingRequiredVariableError) - }) - - it("should throw if required value doesn't exist", () => { - const builder = new EnvVariableBuilder('required', undefined) - - expect(() => builder.required()).toThrow(MissingRequiredVariableError) - }) - - it.each([ - [true, 'true'], - [false, 'false'], - ['true', 'true'], - ['false', 'false'], - [0, '0'], - [123, '123'], - [-123, '-123'], - [1.23, '1.23'], - [0.23, '0.23'], - ['some string', 'some string'], - ['', ''], - ])( - "should set the default value [%s] if env value doesn't exist", - (def, expected) => { - const builder = new EnvVariableBuilder('default') - - expect(builder.default(def).string()).toEqual(expected) - } - ) - - it.each([ - [true], - [false], - ['true'], - ['false'], - [0], - [123], - [-123], - [1.23], - [0.23], - ['some string'], - [''], - ])('should not set the default value [%s] if env value exist', (def) => { - const builder = new EnvVariableBuilder('default_1', 'variable') - - expect(builder.default(def).string()).toEqual('variable') - }) - - it.each([ - ['', ''], - [' ', ''], - [' ', ''], - [' ', ''], - [' test ', 'test'], - [' 123', '123'], - ['true ', 'true'], - ])('should trim the value [%s] -> [%s]', (value, expected) => { - const builder = new EnvVariableBuilder('trim', value) - - expect(builder.trim().string()).toEqual(expected) - }) - - it.each([ - ['0', -10, 10, 0], - ['-10', -10, 10, -10], - ['10', -10, 10, 10], - ['1.99', 1, 2, 1.99], - ['-1.99', -2, -1, -1.99], - ['0', 0, 0, 0], - ['9999', Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, 9999], - ])( - 'should validate range of the value %s > %s && < %s -> %s', - (value, from, to, expected) => { - const builder = new EnvVariableBuilder('range', value) - - expect(builder.range(from, to)).toEqual(expected) - } - ) - - it('should not validate range if value is undefined', () => { - const builder = new EnvVariableBuilder('range', undefined) - - expect(builder.range(1, -1)).toEqual(NaN) - }) - - it.each([ - ['100', -10, 10], - ['-1', 0, 10], - ['0', 10, -10], - ])( - 'should validate range of the value %s > %s && < %s and throw an UnexpectedValueError', - (value, from, to) => { - const builder = new EnvVariableBuilder('range', value) - - expect(() => builder.range(from, to)).toThrow(UnexpectedValueError) - } - ) -}) diff --git a/src/__tests__/VariableBuilder.test.ts b/src/__tests__/VariableBuilder.test.ts new file mode 100644 index 0000000..e4aac28 --- /dev/null +++ b/src/__tests__/VariableBuilder.test.ts @@ -0,0 +1,152 @@ +import { VariableBuilder } from '../VariableBuilder' + +describe('VariableBuilder', () => { + enum TestStringEnum { + ENUM_VALUE = 'e_value', + } + + enum TestNumericEnum { + ENUM_VALUE_1, + ENUM_VALUE_2, + ENUM_VALUE_5 = 5, + } + + it('should read value from environment', () => { + const builder = new VariableBuilder('TEST_KEY', 'TEST_VALUE') + + expect(builder.string()).toEqual('TEST_VALUE') + }) + + it.each([ + ['S1', 'Some String Value', 'Some String Value'], + ['S2', '123', '123'], + ['S3', '', ''], + ])( + '%s should correctly read string value `%s` == `%s`', + (key: string, value: string | undefined, result: string) => { + const builder = new VariableBuilder(key, value) + + expect(builder.string()).toEqual(result) + expect(builder.toString()).toEqual(result) + } + ) + + it.each([ + ['B1', 'true', true], + ['B2', 'false', false], + ['B3', 'some random string', false], + ['B4', '123', false], + ['B5', '1', false], + ['B6', '0', false], + ['B7', '', false], + ])( + '%s should correctly read boolean value `%s` == `%s`', + (key: string, value: string, result: boolean) => { + const builder = new VariableBuilder(key, value) + + expect(builder.boolean()).toEqual(result) + } + ) + + it.each([ + ['N1', '123', 123], + ['N2', '-123', -123], + ['N3', '0.23', 0.23], + ['N4', '2.99', 2.99], + ['N5', '.65', 0.65], + ['N6', '', 0], + ['N7', 'some random string', NaN], + ['N8', '1a2', NaN], + ['N9', 'a1', NaN], + ['N10', '1a', NaN], + ])( + '%s should correctly read number value `%s` == `%s`', + (key: string, value: string, result: number) => { + const builder = new VariableBuilder(key, value) + + expect(builder.number()).toEqual(result) + } + ) + + it('should correctly read string Enum value', () => { + const key = Object.keys(TestStringEnum)[0] + const value = Object.values(TestStringEnum)[0] + const builder = new VariableBuilder(key, value) + + expect(builder.enum(TestStringEnum)).toEqual(value) + }) + + it("should throw Error if string Enum value doesn't exist", () => { + const builder = new VariableBuilder('TestStringEnum', 'invalid') + + expect(() => builder.enum(TestStringEnum)).toThrow(Error) + }) + + it('should correctly read number Enum value', () => { + const value = '5' + const builder = new VariableBuilder('TestNumericEnum1', value) + + expect(builder.enum(TestNumericEnum)).toEqual(value) + }) + + it("should throw Error if numeric Enum value doesn't exist", () => { + const builder = new VariableBuilder('TestNumericEnum2', '4') + + expect(() => builder.enum(TestNumericEnum)).toThrow(Error) + }) + + it('should not throw Error if required value exist', () => { + const builder = new VariableBuilder('required', 'value') + + expect(() => builder.string()).not.toThrow(Error) + }) + + it("should throw if required value doesn't exist", () => { + const builder = new VariableBuilder('required', undefined) + + expect(() => builder.string()).toThrow(Error) + }) + + it.each([ + [['a', 'b', 'c']], + [{ a: 1, b: 2, c: 3}], + ['"string"'] + ])( + 'should decode JSON: %s', + (value) => { + const builder = new VariableBuilder('range', JSON.stringify(value)) + + expect(builder.json()).toEqual(value) + } + ) + + it.each([ + ['0', -10, 10], + ['-10', -10, 10], + ['10', -10, 10], + ['1.99', 1, 2], + ['-1.99', -2, -1], + ['0', 0, 0], + ['9999', Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + ])( + 'should validate range of the value %s >= %s and <= %s -> %s', + (value, from, to) => { + const builder = new VariableBuilder('range', value) + + expect(builder.range(from, to)).toEqual(Number(value)) + } + ) + + it.each([ + ['100', -10, 10], + ['-1', 0, 10], + ['0', 10, -10], + ])( + 'should validate range of the value %s > %s && < %s and throw an Error', + (value, from, to) => { + const builder = new VariableBuilder('range', value) + + expect(() => builder.range(from, to)).toThrow(Error) + } + ) +}) diff --git a/src/__tests__/env.ts b/src/__tests__/env.ts index 098ec0c..ae1492a 100644 --- a/src/__tests__/env.ts +++ b/src/__tests__/env.ts @@ -1,25 +1,66 @@ import { env } from '../env' -jest.mock('../EnvVariableBuilder') -const { EnvVariableBuilder: EnvVariableBuilderMock } = jest.requireMock( - '../EnvVariableBuilder' +jest.mock('../VariableBuilder') +const { VariableBuilder } = jest.requireMock( + '../VariableBuilder' ) +const testEnvKey = 'KAMINOONI_ENV_TEST_VARIABLE' + describe('env', () => { beforeEach(() => { - EnvVariableBuilderMock.mockClear() + VariableBuilder.mockClear() + delete process.env[testEnvKey] }) - it.each([['test'], ['123'], ['true'], ['df13']])( - 'should read env variable and return EnvVariableBuilder for [%s] value', + it.each([['test'], ['123'], ['true'], ['df13'], ['default']])( + 'should read env variable and return VariableBuilder for [%s] value', (value) => { - process.env['test'] = value - env('test') + process.env[testEnvKey] = value + env(testEnvKey) - expect(EnvVariableBuilderMock).toBeCalledWith('test', value) - expect(EnvVariableBuilderMock).toBeCalledTimes(1) + expect(VariableBuilder).toBeCalledWith(testEnvKey, value) + expect(VariableBuilder).toBeCalledTimes(1) + } + ) - delete process.env['test'] + it.each([ + [true, 'true'], + [false, 'false'], + ['true', 'true'], + ['false', 'false'], + [0, '0'], + [123, '123'], + [-123, '-123'], + [1.23, '1.23'], + [0.23, '0.23'], + ['some string', 'some string'], + ['', ''], + ])( + "should set the default value [%s] if env value doesn't exist", + (def, expected) => { + env(testEnvKey, def) + expect(VariableBuilder).toBeCalledWith(testEnvKey, expected) + expect(VariableBuilder).toBeCalledTimes(1) } ) + + it.each([ + [true], + [false], + ['true'], + ['false'], + [0], + [123], + [-123], + [1.23], + [0.23], + ['some string'], + [''], + ])('should not set the default value [%s] if env value exist', (def) => { + process.env[testEnvKey] = 'value' + env(testEnvKey, def) + expect(VariableBuilder).toBeCalledWith(testEnvKey, 'value') + expect(VariableBuilder).toBeCalledTimes(1) + }) }) diff --git a/src/env.ts b/src/env.ts index 31079ae..0376aa4 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,7 +1,15 @@ -import { EnvVariableBuilder } from './EnvVariableBuilder' +import { VariableBuilder } from "./VariableBuilder"; + +const simpleTypes = ["string", "number", "boolean", "undefined"]; /** * @param key Env variable name + * @param defaultValue Default env variable */ -export const env = (key: string) => - new EnvVariableBuilder(key, process.env[key]) +export const env = (key: string, defaultValue?: T) => { + let fallback = simpleTypes.includes(typeof defaultValue) + ? defaultValue?.toString() + : JSON.stringify(defaultValue) + + return new VariableBuilder(key, process.env[key] ?? fallback); +}; diff --git a/src/errors/EnvVariableBuilderError.ts b/src/errors/EnvVariableBuilderError.ts deleted file mode 100644 index 88bb72e..0000000 --- a/src/errors/EnvVariableBuilderError.ts +++ /dev/null @@ -1 +0,0 @@ -export abstract class EnvVariableBuilderError extends Error {} diff --git a/src/errors/MissingRequiredVariableError.ts b/src/errors/MissingRequiredVariableError.ts deleted file mode 100644 index a14a753..0000000 --- a/src/errors/MissingRequiredVariableError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { EnvVariableBuilderError } from './EnvVariableBuilderError' - -export class MissingRequiredVariableError extends EnvVariableBuilderError { - constructor(name: string) { - super(`Required env variable '${name}' not found`) - } -} diff --git a/src/errors/UnexpectedValueError.ts b/src/errors/UnexpectedValueError.ts deleted file mode 100644 index 409b433..0000000 --- a/src/errors/UnexpectedValueError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { EnvVariableBuilderError } from './EnvVariableBuilderError' - -export class UnexpectedValueError extends EnvVariableBuilderError { - constructor(name: string, value?: string) { - super(`Unexpected env variable value '${name}=${value}'`) - } -} diff --git a/src/errors/__tests__/MissingRequiredVariableError.test.ts b/src/errors/__tests__/MissingRequiredVariableError.test.ts deleted file mode 100644 index 7ace5b2..0000000 --- a/src/errors/__tests__/MissingRequiredVariableError.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MissingRequiredVariableError } from '../MissingRequiredVariableError' - -describe('MissingRequiredVariableError', () => { - it('should have correct error message', () => { - const error = new MissingRequiredVariableError('TEST_VAR') - - expect(error.message).toEqual("Required env variable 'TEST_VAR' not found") - }) -}) diff --git a/src/errors/__tests__/UnexpectedValueError.test.ts b/src/errors/__tests__/UnexpectedValueError.test.ts deleted file mode 100644 index c57010d..0000000 --- a/src/errors/__tests__/UnexpectedValueError.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UnexpectedValueError } from '../UnexpectedValueError' - -describe('UnexpectedValueError', () => { - it('should have correct error message', () => { - const error = new UnexpectedValueError('TEST_VAR', 'TEST_VALUE') - - expect(error.message).toEqual( - `Unexpected env variable value 'TEST_VAR=TEST_VALUE'` - ) - }) -}) diff --git a/src/errors/index.ts b/src/errors/index.ts deleted file mode 100644 index 5b19e71..0000000 --- a/src/errors/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './EnvVariableBuilderError' -export * from './MissingRequiredVariableError' -export * from './UnexpectedValueError' diff --git a/src/index.ts b/src/index.ts index c8f0a1c..40238d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1 @@ export { env } from './env' -export * from './errors'