Comparing version 1.3.1 to 1.4.0
@@ -1,2 +0,2 @@ | ||
export declare function Env(variableName?: string): any; | ||
export declare function Env(variableName?: string | string[]): any; | ||
//# sourceMappingURL=env.d.ts.map |
@@ -10,24 +10,37 @@ "use strict"; | ||
} | ||
const FALSEY_VALUES = ['false', '0']; | ||
function Env(variableName) { | ||
return (target, key) => { | ||
variableName !== null && variableName !== void 0 ? variableName : (variableName = upperSnakeCase(key)); | ||
let value = process.env[variableName]; | ||
if (typeof value === 'undefined') { | ||
let envVarValue = void 0; | ||
if (Array.isArray(variableName)) { | ||
const existingKey = variableName.find((k) => typeof process.env[k] === 'string'); | ||
if (typeof existingKey === 'string') { | ||
envVarValue = process.env[existingKey]; | ||
} | ||
} | ||
else { | ||
envVarValue = process.env[variableName]; | ||
} | ||
let outValue; | ||
if (typeof envVarValue === 'undefined') { | ||
if (typeof target[key] === 'undefined') { | ||
throw new Error(`Environment variable ${variableName} is undefined`); | ||
throw new Error(`CLASSENV: Environment variable ${JSON.stringify(variableName)} is undefined and default value for field ${JSON.stringify(key)} not set`); | ||
} | ||
else { | ||
value = target[key]; | ||
outValue = target[key]; | ||
} | ||
} | ||
const designType = Reflect.getMetadata('design:type', target, key); | ||
if ([String, Number, Boolean].includes(designType)) { | ||
if (designType === Boolean && ['false', 'FALSE', '0'].includes(value)) { | ||
value = false; | ||
else { | ||
outValue = envVarValue; | ||
} | ||
const DesignType = Reflect.getMetadata('design:type', target, key); | ||
if ([String, Number, Boolean].includes(DesignType)) { | ||
if (DesignType === Boolean && | ||
FALSEY_VALUES.includes(outValue.toLowerCase())) { | ||
outValue = ''; | ||
} | ||
else { | ||
value = designType(value); | ||
} | ||
outValue = DesignType(outValue); | ||
Object.defineProperty(target, key, { | ||
value, | ||
value: outValue, | ||
writable: false, | ||
@@ -39,3 +52,3 @@ enumerable: true, | ||
else { | ||
throw new Error(`${key} type must be one of [String, Number, Boolean]. Got ${designType.name}`); | ||
throw new Error(`CLASSENV: ${key} type must be one of [String, Number, Boolean]. Got ${DesignType.name}`); | ||
} | ||
@@ -42,0 +55,0 @@ }; |
{ | ||
"name": "classenv", | ||
"version": "1.3.1", | ||
"description": "Describe your environment variables contract with TypeScript class", | ||
"version": "1.4.0", | ||
"description": "Describe your environment variables contract with TypeScript class decorator", | ||
"main": "dist/index.js", | ||
@@ -27,5 +27,5 @@ "types": "dist/index.d.ts", | ||
"jest": "^25.1.0", | ||
"prettier": "^2.6.2", | ||
"prettier": "^2.7.1", | ||
"ts-jest": "^25.2.1", | ||
"typescript": "^4.6.4" | ||
"typescript": "^4.8.4" | ||
}, | ||
@@ -36,7 +36,10 @@ "peerDependencies": { | ||
"keywords": [ | ||
"typescript", | ||
"class", | ||
"environment", | ||
"env", | ||
"variables", | ||
"decorator", | ||
"dotenv", | ||
"env", | ||
".env", | ||
"environment", | ||
"variables", | ||
"config", | ||
@@ -46,7 +49,4 @@ "settings", | ||
"process.env", | ||
"typescript", | ||
"class", | ||
"decorator", | ||
"defaults" | ||
] | ||
} |
156
README.md
@@ -1,4 +0,4 @@ | ||
# classenv | ||
# TypeScript environment variable decorator | ||
A perfect typescript class environment variables library. | ||
A perfect TypeScript environment variables library. | ||
@@ -11,73 +11,143 @@ - Strongly-typed declarative class containing your environment data | ||
- Throws runtime error if variable doesn't exist | ||
- Default values support | ||
- Supports default values | ||
- Makes decorated properties read-only in runtime | ||
- ❤️ You will like it | ||
## 💼 Use cases | ||
## Description | ||
Let's pretend we have very simple | ||
### 🪞 Type-casting Using TypeScript metadata reflection | ||
**.env** | ||
Just specify class field type and `classenv` will cast the environment variable string value to the value of your field type. | ||
Only `string`, `number`, and `boolean` is supported. | ||
```ts | ||
process.env['PORT'] = '3000'; | ||
class ServerSettings { | ||
// Field name will be auto-converted to POSTGRES_URL for checking the process.env | ||
@Env('PORT') | ||
portNumber!: number; // 3000 | ||
@Env('PORT') | ||
portString!: string; // "3000" | ||
@Env('PORT') // Why not?! | ||
portBoolean!: boolean; // true | ||
} | ||
``` | ||
IS_SOMETHING_ENABLED=1 | ||
### 🐍 Auto UPPER_SNAKE_CASE from camelCase conversion | ||
No need to manually specify the environment variable name | ||
```ts | ||
process.env['POSTGRES_URL'] = 'postgres://127.0.0.1:5432'; | ||
class PostgresAdapter { | ||
// Field name will be auto-converted to POSTGRES_URL for checking the process.env | ||
@Env() | ||
postgresUrl!: string; // postgres://127.0.0.1:5432 | ||
} | ||
``` | ||
How can we describe it using **classenv** | ||
### 🫙 Use default value in case of environment variable absence | ||
**environment.ts** | ||
```typescript | ||
import { Env } from 'classenv'; | ||
```ts | ||
class ServerSettings { | ||
@Env() | ||
port: number = 3000; | ||
} | ||
``` | ||
export class Environment { | ||
@Env() // Auto UPPER_SNAKE_CASE conversion | ||
static isSomethingEnabled: number; // process.env.IS_SOMETHING_ENABLED | ||
### 🚔 Throw runtime error if no value provided | ||
@Env() // Instance properties supported | ||
isSomethingEnabled: number; | ||
One could say `"It's a bad practice to throw runtime error"`, and it's a right assertion, but not in this case. | ||
Most of the time your application can't work without all the environment variables. | ||
You don't want to run application in an indefinite state and then debug these strange things. | ||
So `classenv` will throw runtime error and your application should shut down with an informative message of what's going wrong. | ||
@Env() // Won't throw, because got default value | ||
static withDefault: string = 'yeah its me' | ||
```ts | ||
class PostgresAdapter { | ||
@Env() | ||
// Will throw a runtime error, because your app can't work without DB connection | ||
postgresUrl!: string; | ||
} | ||
``` | ||
@Env('IS_SOMETHING_ENABLED') | ||
static isEnabledStr: string; | ||
But in case the environment variable is not required – you can just assign a default value for the field, and it will not throw. | ||
@Env('IS_SOMETHING_ENABLED') | ||
static isEnabledNmbr: number; | ||
```ts | ||
class PostgresAdapter { | ||
@Env() | ||
postgresUrl: string = 'postgres://127.0.0.1:5432'; // Everything is ok here | ||
} | ||
``` | ||
@Env('IS_SOMETHING_ENABLED') | ||
static isEnabledBln: boolean; | ||
### 🔘 Pick one of the name from array | ||
```ts | ||
process.env['POSTGRES_URL'] = 'postgres://127.0.0.1:5432'; | ||
class PostgresAdapter { | ||
@Env(['POSTGRESQL_URI', 'PG_URL', 'POSTGRES_URL']) | ||
url!: string; // postgres://127.0.0.1:5432 | ||
} | ||
``` | ||
`@Env` property data type should be scalar (string, number or boolean). | ||
### ✨ `static` field also supported | ||
**main.ts** | ||
```typescript | ||
import {Environment} from './environment.ts' | ||
```ts | ||
process.env['PORT'] = '3000'; | ||
console.log(typeof Environment.isEnabledStr, Environment.isEnabledStr) | ||
// string 1 | ||
class ServerSettings { | ||
@Env() | ||
static port: number = 3000; | ||
} | ||
``` | ||
console.log(typeof Environment.isEnabledNmbr, Environment.isEnabledNmbr) | ||
// number 1 | ||
### 1️⃣ Boolean type casting 0️⃣ | ||
console.log(typeof Environment.isEnabledBln, Environment.isEnabledBln) | ||
// boolean true | ||
If value is `0` of `false` in any case (`FaLsE` also included, since it's `.toLowerCase()`'d under the hood) – it becomes `false`. | ||
Otherwise - `true` | ||
console.log(typeof Environment.isSomethingEnabled, Environment.isSomethingEnabled) | ||
// number 1 | ||
```ts | ||
process.env['FALSE'] = 'false'; | ||
process.env['ZERO'] = '0'; | ||
process.env['TRUE'] = 'true'; | ||
process.env['ANYTHING'] = 'Jast a random string'; | ||
Environment.isEnabledBln = false; | ||
// TypeError: Cannot assign to read only property 'isEnabledBln' of function 'class Test{}' | ||
class Common { | ||
@Env() | ||
static FALSE!: boolean; // false | ||
@Env() | ||
static zero!: boolean; // false | ||
@Env() | ||
static TRUE!: boolean; // true | ||
@Env() | ||
static anything!: boolean; // true | ||
} | ||
``` | ||
### 🛑 `@Env()` decorated properties are read-only in runtime | ||
// Let's check instance properties | ||
const env = new Environment(); | ||
Environment is something established from outside, so you definitely should not modify it in your application. | ||
console.log(env.isSomethingEnabled) // 1 | ||
```ts | ||
process.env['PORT'] = '3000'; | ||
class ServerSettings { | ||
@Env() | ||
static port!: number; | ||
} | ||
// TypeError: Cannot assign to read only property 'port' of function 'class ServerSettings{}' | ||
ServerSettings.port = 5000; | ||
``` | ||
## Dependencies | ||
## ❗Dependencies❗ | ||
It is important, `classenv` can not work without it. | ||
### reflect-metadata | ||
``` | ||
@@ -87,3 +157,3 @@ npm i reflect-metadata | ||
And then import it somewhere close to your entry point (`index.ts`/`main.ts`/etc...). | ||
And then import it somewhere close to your entry point (`index.ts`/`main.ts`/etc...). | ||
Should be imported before any of your environment classes. | ||
@@ -90,0 +160,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10412
74
171