@wcaservices/config 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wcaservices/config",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "Jørgen Vatle <Jorgen@WcAServices.net> (https://github.com/JorgenVatle)",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "vitest",
|
|
11
|
+
"build": "tsdown",
|
|
12
|
+
"lint": "tsgo --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.cjs",
|
|
21
|
+
"default": "./dist/index.mjs"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.0.8",
|
|
25
|
+
"@wcaservices/tsconfig": "^1.0.0",
|
|
26
|
+
"@wcaservices/tsdown": "^1.0.0",
|
|
27
|
+
"tsdown": "^0.20.0-beta.3",
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"vitest": "^3.2.4"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@wcaservices/util": "^2.1.1-beta.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"valibot": "^1.2.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"valibot": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { expectTypeOf, describe, it } from 'vitest';
|
|
2
|
+
import { defineEnvironmentVariables } from './defineEnvironmentVariables';
|
|
3
|
+
|
|
4
|
+
describe('Type inference for string types', () => {
|
|
5
|
+
const env = defineEnvironmentVariables({
|
|
6
|
+
NON_REQUIRED_ENV_VAR: {},
|
|
7
|
+
REQUIRED_ENV_VAR: { required: true },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('Only yields the configured variables', () => {
|
|
11
|
+
expectTypeOf(env).toMatchTypeOf<{
|
|
12
|
+
REQUIRED_ENV_VAR: string,
|
|
13
|
+
NON_REQUIRED_ENV_VAR: string | undefined
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
expectTypeOf(env).not.toMatchTypeOf<{
|
|
17
|
+
REQUIRED_ENV_VAR: string,
|
|
18
|
+
NON_REQUIRED_ENV_VAR: string | undefined,
|
|
19
|
+
UNKNOWN_REQUIRED_ENV_VAR: string,
|
|
20
|
+
UNKNOWN_NON_REQUIRED_ENV_VAR: string | undefined
|
|
21
|
+
}>();
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('Should yield string or undefined for non-required variables', () => {
|
|
25
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).toEqualTypeOf<string | undefined>();
|
|
26
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).not.toEqualTypeOf<string>();
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it(`Should omit 'undefined' type for required variables`, () => {
|
|
30
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).toEqualTypeOf<string>();
|
|
31
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
32
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
33
|
+
});
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('Type inference for NUMBER types', () => {
|
|
37
|
+
const env = defineEnvironmentVariables({
|
|
38
|
+
NON_REQUIRED_ENV_VAR: { type: 'NUMBER' },
|
|
39
|
+
REQUIRED_ENV_VAR: { type: 'NUMBER', required: true },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('Only yields the configured variables', () => {
|
|
43
|
+
expectTypeOf(env).toMatchTypeOf<{
|
|
44
|
+
REQUIRED_ENV_VAR: number,
|
|
45
|
+
NON_REQUIRED_ENV_VAR: number | undefined
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
expectTypeOf(env).not.toMatchTypeOf<{
|
|
49
|
+
REQUIRED_ENV_VAR: number,
|
|
50
|
+
NON_REQUIRED_ENV_VAR: number | undefined,
|
|
51
|
+
UNKNOWN_REQUIRED_ENV_VAR: number,
|
|
52
|
+
UNKNOWN_NON_REQUIRED_ENV_VAR: number | undefined
|
|
53
|
+
}>();
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('Should yield number or undefined for non-required variables', () => {
|
|
57
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).toEqualTypeOf<number | undefined>();
|
|
58
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).not.toEqualTypeOf<number>();
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it(`Should omit 'undefined' type for required variables`, () => {
|
|
62
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).toEqualTypeOf<number>();
|
|
63
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
64
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
describe('Type inference for BOOLEAN types', () => {
|
|
70
|
+
const env = defineEnvironmentVariables({
|
|
71
|
+
NON_REQUIRED_ENV_VAR: { type: 'BOOLEAN' },
|
|
72
|
+
REQUIRED_ENV_VAR: { type: 'BOOLEAN', required: true },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('Only yields the configured variables', () => {
|
|
76
|
+
expectTypeOf(env).toMatchTypeOf<{
|
|
77
|
+
REQUIRED_ENV_VAR: boolean,
|
|
78
|
+
NON_REQUIRED_ENV_VAR: boolean | undefined
|
|
79
|
+
}>();
|
|
80
|
+
|
|
81
|
+
expectTypeOf(env).not.toMatchTypeOf<{
|
|
82
|
+
REQUIRED_ENV_VAR: boolean,
|
|
83
|
+
NON_REQUIRED_ENV_VAR: boolean | undefined,
|
|
84
|
+
UNKNOWN_REQUIRED_ENV_VAR: boolean,
|
|
85
|
+
UNKNOWN_NON_REQUIRED_ENV_VAR: boolean | undefined
|
|
86
|
+
}>();
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('Should yield boolean or undefined for non-required variables', () => {
|
|
90
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).toEqualTypeOf<boolean | undefined>();
|
|
91
|
+
expectTypeOf(env.NON_REQUIRED_ENV_VAR).not.toEqualTypeOf<boolean>();
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it(`Should omit 'undefined' type for required variables`, () => {
|
|
95
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).toEqualTypeOf<boolean>();
|
|
96
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
97
|
+
expectTypeOf(env.REQUIRED_ENV_VAR).not.toEqualTypeOf<undefined>();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { envFlag } from '@wcaservices/util/lib';
|
|
2
|
+
|
|
3
|
+
export function defineEnvironmentVariables<
|
|
4
|
+
TVariables extends VariableDefinitionMap
|
|
5
|
+
>(variables: TVariables): {
|
|
6
|
+
[key in keyof TVariables]: VariableOutputType<TVariables[key]>;
|
|
7
|
+
} {
|
|
8
|
+
const result = {};
|
|
9
|
+
|
|
10
|
+
for (const [key, config] of Object.entries(variables)) {
|
|
11
|
+
const originalValue = process.env[key] ?? config.default;
|
|
12
|
+
let value: unknown = originalValue;
|
|
13
|
+
const cast = config.type ?? 'default';
|
|
14
|
+
|
|
15
|
+
if (config.required && typeof value === 'undefined') {
|
|
16
|
+
throw new RequiredEnvironmentVariableError(Object.assign({ name: key }, config));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
switch (cast) {
|
|
20
|
+
case 'BOOLEAN':
|
|
21
|
+
value = envFlag(key);
|
|
22
|
+
break;
|
|
23
|
+
case 'NUMBER':
|
|
24
|
+
value = Number(value);
|
|
25
|
+
if (Number.isNaN(value)) {
|
|
26
|
+
throw new EnvironmentVariableError(`Invalid number type for '${key}': ${originalValue}`);
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
case 'JSON':
|
|
30
|
+
JSON.parse(String(value));
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Object.assign(result, { [key]: value });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// @ts-expect-error Type mismatch between result and explicit return type
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface EnvironmentVariableConfig<
|
|
42
|
+
TRequired extends boolean = boolean,
|
|
43
|
+
TTypeCast extends keyof VariableTypeCast = keyof VariableTypeCast,
|
|
44
|
+
> {
|
|
45
|
+
required?: TRequired;
|
|
46
|
+
type?: TTypeCast;
|
|
47
|
+
default?: VariableTypeCast[TTypeCast];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type VariableDefinitionMap = {
|
|
51
|
+
[key in keyof NodeJS.ProcessEnv]: EnvironmentVariableConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type VariableOutputType<
|
|
55
|
+
TConfig extends EnvironmentVariableConfig,
|
|
56
|
+
TTypeCast extends keyof VariableTypeCast = TConfig['type'] extends keyof VariableTypeCast
|
|
57
|
+
? TConfig['type']
|
|
58
|
+
: 'default',
|
|
59
|
+
TType = VariableTypeCast[TTypeCast]
|
|
60
|
+
> = TConfig['required'] extends true
|
|
61
|
+
? TType
|
|
62
|
+
: TType | undefined;
|
|
63
|
+
|
|
64
|
+
class EnvironmentVariableError extends Error {}
|
|
65
|
+
|
|
66
|
+
class RequiredEnvironmentVariableError extends EnvironmentVariableError {
|
|
67
|
+
constructor(public readonly config: EnvironmentVariableConfig & { name: string }) {
|
|
68
|
+
super(`Required environment variable "${config.name}" is missing. Make sure one is set before starting the process!`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface VariableTypeCast {
|
|
73
|
+
/**
|
|
74
|
+
* Default type for environment variables.
|
|
75
|
+
*/
|
|
76
|
+
default: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Boolean flag (1, 0, true, false, yes, no)
|
|
80
|
+
*/
|
|
81
|
+
BOOLEAN: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Cast inputs to number.
|
|
85
|
+
*/
|
|
86
|
+
NUMBER: number;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse input as JSON
|
|
90
|
+
*/
|
|
91
|
+
JSON: unknown; // todo
|
|
92
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './defineEnvironmentVariables';
|