Yieldless
Reference

yieldless/env

Tuple helpers for required environment variables and schema-backed config parsing.

yieldless/env keeps configuration loading explicit without inventing a config framework. It helps with the parts that are easy to get wrong: missing variables, empty variables, selecting only the keys a service cares about, and handing the result to the same schema adapters used elsewhere.

Exports

  • readEnv(source, key, options): SafeResult<string, EnvVarError>
  • readOptionalEnv(source, key, options): SafeResult<string | undefined, EnvVarError>
  • pickEnv(source, keys): PickedEnv
  • parseEnvSafe(schema, source = process.env): SafeResult<T, E>
  • parseEnvAsyncSafe(schema, source = process.env): Promise<SafeResult<T, E>>
  • class EnvVarError extends Error

Required values

import { readEnv } from "yieldless/env";

const [error, databaseUrl] = readEnv(process.env, "DATABASE_URL");

if (error) {
  return [error, null] as const;
}

Empty strings are treated as errors by default. Pass allowEmpty: true when an empty string is meaningful.

Schema-backed config

import { parseEnvSafe, pickEnv } from "yieldless/env";

const [error, env] = parseEnvSafe(
  envSchema,
  pickEnv(process.env, ["DATABASE_URL", "PORT", "NODE_ENV"] as const),
);

The schema can be anything supported by yieldless/schema: safeParse(), parse(), safeParseAsync(), or parseAsync().

Behavior notes

  • parseEnvSafe() defaults to process.env when it exists.
  • pickEnv() is useful for tests and for avoiding accidental coupling to unrelated variables.
  • EnvVarError.code is either ERR_ENV_MISSING or ERR_ENV_EMPTY.
  • readOptionalEnv() returns [null, undefined] for missing values but still rejects empty strings unless allowEmpty is enabled.

Good

Validate configuration once at startup or at the outer service boundary.

const [error, config] = parseEnvSafe(
  configSchema,
  pickEnv(process.env, ["DATABASE_URL", "PORT", "NODE_ENV"] as const),
);

if (error) {
  console.error(error);
  process.exitCode = 1;
}

Use readEnv() for one-off scripts where a full schema would add noise.

const [tokenError, token] = readEnv(process.env, "GITHUB_TOKEN");
if (tokenError) return [tokenError, null] as const;

Avoid

Do not read process.env deep inside business logic.

export async function sendEmail(user: User) {
  const apiKey = process.env.MAILER_API_KEY;
  return mailer.send(apiKey, user.email);
}

Parse once, then pass config explicitly or inject it at the edge.

On this page