/**
* A string-indexable object that returns the string value from an environment variable, or undefined if it does not exist.
*/
export const env = new Proxy({} as Record<string, string | undefined>, {
get(_target, prop, _receiver) {
return Deno.env.get(prop.toString());
}
})
export const GenerateNamespaced = (namespace?: string) => {
const prefix = namespace ? `${namespace}_` : '';
/**
* Returns a string from the environment.
*
* Is sugar syntax for `env[key]` or `Deno.env.get(key)`
*/
function string(key: string): string | undefined
function string<T extends string | undefined>(key: string, default_value: T): TypedResponse<T, string>
function string(key: string, default_value?: string) {
return env[prefix + key] || default_value;
}
/**
* ---
*
* Returns `true` if the environment variable is one of the following truthy values: (Case insensitive)
*
* `true`, `yes`, `y`, `on`, `1`
*
* @param key The environment variable to use to find the truthy value.
*/
function boolean(key: string) {
const _key = prefix + key;
return env[_key] !== undefined && ['true', '1', 'yes', 'y', 'on'].includes(env[_key].toLowerCase())
}
type TypedResponse<T, S> = T extends S ? S : S | undefined;
/**
* ---
*
* Returns a numerical value from an environment variable. If it does not exist or is not a valid number, it returns either the fallback value or undefined.
*
* Accepts a `fallback`, as well as `min` and `max` bounds if required.
*
* @param key The environment variable to use to find the numerical value.
*/
function number(key: string): number | undefined
function number<T extends number | undefined>(key: string, default_value: T): TypedResponse<T, number>
function number<T extends number | undefined>(key: string, default_value: T, min: number, max: number): TypedResponse<T, number>
function number(key: string, default_value?: number, min?: number, max?: number) {
let num = Number(env[prefix + key]);
if(isNaN(num)) return default_value;
if(typeof min == 'number' && num < min) num = min;
if(typeof max == 'number' && num > max) num = max;
return num;
}
/**
* ---
*
* Returns an integral value from an environment variable. If it does not exist or is not a valid integer, it returns either the fallback value or undefined.
*
* Accepts a `fallback`, as well as `min` and `max` bounds if required.
*
* @param key The environment variable to use to find the integer value.
* @param default_port The fallback value if the environment does not contain the key, or the value is invalid. Default: `8080`
* @param min The minimum acceptable value to allow
* @param min The maximum acceptable value to allow
*/
function integer(key: string): number | undefined
function integer<T extends number | undefined>(key: string, default_value: T): TypedResponse<T, number>
function integer<T extends number | undefined>(key: string, default_value: T, min: number, max: number): TypedResponse<T, number>
function integer(key: string, default_value?: number, min?: number, max?: number) {
let num = number(key, default_value as number);
if(!num) return undefined;
if(!Number.isSafeInteger(num) && default_value) num = default_value;
if(typeof min == 'number' && num < min) num = min;
if(typeof max == 'number' && num > max) num = max;
return ~~num;
}
/**
* ---
*
* Returns a port-friendly integral value from an environment variable.
*
* An optional `fallback` argument is accepted. An invalid `fallback` value will throw an error.
*
* @param key The environment variable to use to find the port number. Default: `PORT`
* @param default_port The fallback value if the environment does not contain the key, or the value is invalid. Default: `8080`
*/
function port(key: string = 'PORT', default_port: number = 8080): number {
if(!Number.isSafeInteger(default_port)) throw new Error('Default port is not an integer!');
if(default_port < 0) throw new Error('Default port is negative');
if(default_port > 65535) throw new Error('Default port is negative');
const port = integer(key, default_port);
if(port < 0) return default_port;
if(port > 65535) return default_port;
return port;
}
/**
* Adds a layer of namespacing. Each namespace is delimited by underscores
*/
const namespaced = (inner_namespace: string) => GenerateNamespaced(prefix + inner_namespace)
return {
string,
get: string,
boolean,
bool: boolean,
number,
float: number,
integer,
int: integer,
port,
namespaced,
[Symbol.dispose]() {}
}
}
/**
* A wrapper containing helpful methods for retrieving environment variables of specific data types, with automatic type formatting, type safety and fallback values.
*/
const ENV = GenerateNamespaced();
export const string = ENV.string;
export const integer = ENV.integer;
export const number = ENV.number;
export const port = ENV.port;
export const boolean = ENV.boolean;
export default ENV;