Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ env.list('ALLOWED_ORIGINS'); // Shorthand for getList()

// Get a numeric list
env.list('VALUES', { cast: 'number' }); // [1, 2, 3] (converts from '1,2,3')

// Get a duration in milliseconds
// CACHE_TTL=5m
env.getDuration('CACHE_TTL'); // 300000
env.duration('CACHE_TTL'); // Shorthand for getDuration()

// With a default — strings are parsed, numbers are treated as ms
env.getDuration('CACHE_TTL', '30s'); // 30000 if CACHE_TTL is not set
env.getDuration('CACHE_TTL', 1000); // 1000 if CACHE_TTL is not set

// Supported units: ms, s, m, h, d, w (case-insensitive, decimals allowed)
// Returns null if the value and default are both unparseable.
```

#### URLs and IPs
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "good-env",
"version": "7.6.3",
"version": "7.7.0",
"description": "Better environment variable handling for Twelve-Factor node apps",
"main": "src/index.js",
"scripts": {
Expand Down
18 changes: 18 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ declare module "good-env" {
*
*/
export const num: (key: any, defaultVal?: any) => any;
/**
* @description Fetches the value at the given key and parses it as a
* duration string (e.g. "500ms", "30s", "5m", "2h", "1d", "1w") into
* milliseconds. The unit is case-insensitive and whitespace between the
* number and unit is allowed. If the env value is invalid, the default
* is parsed instead. Numeric defaults are treated as milliseconds.
* Returns null if nothing can be resolved.
*
* @param {string} key - A unique key
* @param {(string|number)} defaultVal - A duration string or number of ms
*
*/
export const getDuration: (key: string, defaultVal?: string | number) => number | null;
/**
* @description An alias function for getDuration()
*
*/
export const duration: (key: string, defaultVal?: string | number) => number | null;
/**
* @description Fetches the value at the given key and attempts to
* coherse it into a list of literal values
Expand Down
44 changes: 44 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ const isObject = x => is(x) === '[object Object]';
const isFunction = x => is(x) === '[object Function]';
const mapNums = items => items.map(t => parseFloat(t));
const validType = item => ['number', 'boolean', 'string'].includes(item);
const DURATION_UNITS = {
ms: 1,
s: 1000,
m: 60 * 1000,
h: 60 * 60 * 1000,
d: 24 * 60 * 60 * 1000,
w: 7 * 24 * 60 * 60 * 1000
};
const DURATION_RE = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)$/i;
const parseDuration = input => {
if (isNumber(input)) return Number.isFinite(input) ? input : null;
if (!isString(input)) return null;
const match = input.trim().match(DURATION_RE);
if (!match) return null;
return parseFloat(match[1]) * DURATION_UNITS[match[2].toLowerCase()];
};
const store = { ...process.env };

module.exports = Object
Expand Down Expand Up @@ -339,6 +355,34 @@ module.exports = Object
return this.getNumber(key, defaultVal);
},

/**
* @description Fetches the value at the given key and parses it as a
* duration string (e.g. "500ms", "30s", "5m", "2h", "1d", "1w") into
* milliseconds. The unit is case-insensitive and whitespace between
* the number and unit is allowed. If the env value is invalid, the
* default is parsed instead. Numeric defaults are treated as
* milliseconds. Returns null if nothing can be resolved.
*
* @param {string} key - A unique key
* @param {(string|number)} defaultVal - A duration string or a number of ms
*
*/
getDuration (key, defaultVal) {
const parsed = parseDuration(this.get(key));
if (parsed !== null) return parsed;
const parsedDefault = parseDuration(defaultVal);
if (parsedDefault !== null) return parsedDefault;
return null;
},

/**
* @description An alias function for getDuration()
*
*/
duration (key, defaultVal) {
return this.getDuration(key, defaultVal);
},

/**
* @description Fetches the value at the given key and attempts to
* coerce it into a list of literal values
Expand Down
10 changes: 10 additions & 0 deletions test/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ AWS_REGION=us-east-1
AWS_SESSION_TOKEN=session
VALID_IP=192.168.1.60
INVALID_IP=nope
DURATION_MS=500ms
DURATION_S=30s
DURATION_M=5m
DURATION_H=2h
DURATION_D=1d
DURATION_W=1w
DURATION_FLOAT=1.5h
DURATION_UPPER=10S
DURATION_SPACED=5 m
DURATION_INVALID=nope
46 changes: 46 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,52 @@ test('returns undefined for exequalsting non-number', (t) => {
t.end();
});

test('getDuration parses every supported unit', (t) => {
t.equals(env.getDuration('DURATION_MS'), 500);
t.equals(env.getDuration('DURATION_S'), 30 * 1000);
t.equals(env.getDuration('DURATION_M'), 5 * 60 * 1000);
t.equals(env.getDuration('DURATION_H'), 2 * 60 * 60 * 1000);
t.equals(env.getDuration('DURATION_D'), 24 * 60 * 60 * 1000);
t.equals(env.getDuration('DURATION_W'), 7 * 24 * 60 * 60 * 1000);
t.end();
});

test('getDuration parses decimals, is case-insensitive, allows whitespace', (t) => {
t.equals(env.getDuration('DURATION_FLOAT'), 1.5 * 60 * 60 * 1000);
t.equals(env.getDuration('DURATION_UPPER'), 10 * 1000);
t.equals(env.getDuration('DURATION_SPACED'), 5 * 60 * 1000);
t.end();
});

test('getDuration returns null for invalid env values', (t) => {
t.equals(env.getDuration('DURATION_INVALID'), null);
t.end();
});

test('getDuration falls back to default when env value is invalid', (t) => {
t.equals(env.getDuration('DURATION_INVALID', '2s'), 2000);
t.end();
});

test('getDuration resolves defaults for missing keys', (t) => {
t.equals(env.getDuration('DURATION_NOPE'), null);
t.equals(env.getDuration('DURATION_NOPE', '1m'), 60 * 1000);
t.equals(env.getDuration('DURATION_NOPE', 1234), 1234);
t.end();
});

test('getDuration returns null for unparseable defaults', (t) => {
t.equals(env.getDuration('DURATION_NOPE', 'garbage'), null);
t.equals(env.getDuration('DURATION_NOPE', NaN), null);
t.equals(env.getDuration('DURATION_NOPE', {}), null);
t.end();
});

test('duration() is an alias for getDuration()', (t) => {
t.equals(env.duration('DURATION_M'), 5 * 60 * 1000);
t.end();
});

test('returns a list of values', (t) => {
let result = env.getList('MY_LIST');
t.equals(result.length, 3);
Expand Down
Loading