I'm attempting to write a typescript helper library to strongly type process.env
so you can do the following. The library with throw if the variable is missing or can't be converted to the correct type.
import { getEnv, num, str } from '@lib/env'
const env = getEnv({
TABLE_NAME: num(),
})
// typeof env is
// const env: Readonly<{
// TABLE_NAME: number;
// }>
I currently have the following code which works
type Validator<T> = (
defaultValue?: T,
) => (processEnvKey: string, processEnvValue?: string) => T
type Validators<T> = { [K in keyof T]: ReturnType<Validator<T[K]>> }
export const str: Validator<string> = (defaultValue) => (
processEnvKey,
processEnvValue,
) => {
if (processEnvValue != null) return processEnvValue
if (defaultValue != null) return defaultValue
throw new Error(
`Environment variable '${processEnvKey}' is required and no default was provided`,
)
}
export const num: Validator<number> = (defaultValue) => (
processEnvKey,
processEnvValue,
) => {
if (processEnvValue != null) {
const processEnvValueAsNumber = Number(processEnvValue)
if (Number.isNaN(processEnvValueAsNumber)) {
throw new Error(
`Environment variable '${processEnvKey}' is required to be numeric but could not parse '${processEnvValue}' as a number`,
)
}
return processEnvValueAsNumber
}
if (defaultValue != null) return defaultValue
throw new Error(
`Environment variable '${processEnvKey}' is required and no default was provided`,
)
}
export const getEnv = <T>(
validators: Validators<T>,
environment = process.env,
): Readonly<T> => {
const result: Partial<T> = {}
for (const processEnvKey in validators) {
const validator = validators[processEnvKey]
result[processEnvKey] = validator(processEnvKey, environment[processEnvKey])
}
return result as Readonly<T>
}
I now have a new requirement where I know in advance all the environment keys that are available as an interface
interface Env {
API_ENDPOINT: any
TABLE_NAME: any
}
So I'm trying the change getEnv
so that the object passed in can only contain the keys found in Env
.
I tried changing getEnv
but I'm getting stuck
export const getEnv = <T extends { [K in keyof Env]?: ????>(
If I change it to
export const getEnv = <T extends { [K in keyof Env]?: unknown>(
I can pass additional keys without the compiler complaining. The following doesn't cause a compiler error even though NON_EXISTING_KEY
is not a key of Env
const env = getEnv({
TABLE_NAME: num(),
NON_EXISTING_KEY: str(),
})