objectenvy / envy
Function: envy()
function envy<T>(config): { [KeyType in string | number | symbol]: UnionToIntersection<[T] extends [unknown[]] ? never : [T] extends [object] ? { [K in string]: [T[K]] extends [unknown[]] ? `${ScreamingSnakeCase<(...), (...)>}` extends "" ? never : Record<`${(...)}`, string> : [(...)[(...)]] extends [object] ? { [K in (...)]: (...) }[(...) & (...)] : [(...)] extends [(...)] ? (...) extends (...) ? (...) : (...) : never }[keyof T & string] : [T] extends [Primitive] ? never : never>[KeyType] };Defined in: objectEnvy.ts:984
Serialize a nested camelCased config object back to a flat SCREAMING_SNAKE_CASE env record.
Type Parameters
| Type Parameter |
|---|
T extends ConfigObject |
Parameters
| Parameter | Type | Description |
|---|---|---|
config | T | A nested camelCased configuration object. |
Returns
{ [KeyType in string | number | symbol]: UnionToIntersection<[T] extends [unknown[]] ? never : [T] extends [object] ? { [K in string]: [T[K]] extends [unknown[]] ? `${ScreamingSnakeCase<(...), (...)>}` extends "" ? never : Record<`${(...)}`, string> : [(...)[(...)]] extends [object] ? { [K in (...)]: (...) }[(...) & (...)] : [(...)] extends [(...)] ? (...) extends (...) ? (...) : (...) : never }[keyof T & string] : [T] extends [Primitive] ? never : never>[KeyType] }
A flat Record<string, string> with SCREAMING_SNAKE_CASE keys and all values stringified.
Remarks
envy is the inverse of objectify: it flattens a nested config tree by joining each key path with underscores and uppercasing the result. All values are stringified — numbers and booleans become their string representations. Arrays are serialized as comma-separated strings (e.g., ['a', 'b'] → 'a,b'). Object items inside arrays are JSON-serialized before joining.
The return type is ToEnv<T>, which preserves string literal and template literal types from the config type all the way into the env record type.
Use When
- You need to spawn a child process and want to pass typed config as env variables.
- You're writing a
.envfile from a config object (e.g., for CI scaffolding or test fixtures). - You use
ToEnv<T>for compile-time validation and need the runtime values to match. - You're round-tripping:
objectify()→ mutate config →envy()→ write back to env.
Avoid When
- You only need the
ToEnv<T>type at compile time — no need to callenvy()at runtime. - The config contains
Date,Map,Set, or class instances —envy()serializes them as[object Object]viaString().
Pitfalls
- NEVER rely on
envy()to round-trip arrays of objects faithfully — BECAUSE object items areJSON.stringify-ed then joined; whenobjectify()re-reads the comma-separated string, it treats it as a string array, not an array of objects. - NEVER pass
nullorundefinedvalues in the config — BECAUSEenvy()silently skipsnull/undefinedentries, leaving no env key for them; the round-trip loses those fields. - NEVER expect
envy()to honour a prefix — BECAUSE it outputs bareSCREAMING_SNAKE_CASEkeys with no prefix. Add the prefix yourself if your deployment expectsAPP_PORTrather thanPORT.
Examples
import { envy } from 'objectenvy';
const config = {
portNumber: 3000,
log: { level: 'debug', path: '/var/log' }
};
const env = envy(config);
// { PORT_NUMBER: '3000', LOG_LEVEL: 'debug', LOG_PATH: '/var/log' }// Round-trip: objectify → mutate → envy
import { objectify, envy } from 'objectenvy';
const config = objectify({ env: process.env, prefix: 'APP' });
const mutated = { ...config, debug: true };
const newEnv = envy(mutated);
// spawn({ env: { ...process.env, ...newEnv } })// Array values are joined as comma-separated strings
import { envy } from 'objectenvy';
const config = { hosts: ['localhost', 'example.com'] };
const env = envy(config);
// { HOSTS: 'localhost,example.com' }