Skip to main content
jg-packages

Search

    Environment

    jg-env

    Strict environment variable readers for process.env or import.meta.env, plus secret files, with errors that name the key and never the value.

    v0.1.1UniversalESMMIT

    npm install jg-env

    Usage

    Read a service’s settings once, at startup, and fail fast when one is missing or malformed:

    ts
    import { createEnvironmentReader } from 'jg-env';
    
    const { readBoolean, readEnum, readInteger, readString } = createEnvironmentReader(process.env);
    
    export const NODE_ENV = readEnum('NODE_ENV', ['development', 'production', 'test']);
    export const PORT = readInteger('PORT'); // only plain decimal digits, so 1e3 and 0x10 fail
    export const DATABASE_HOST = readString('DATABASE_HOST');
    export const MAINTENANCE_MODE = readBoolean('MAINTENANCE_MODE'); // exactly true or false

    The same readers take any record of variables, such as Vite’s import.meta.env, whose built-in fields are booleans: only string values count, and anything else is invalid. A variable that’s absent, empty, or blank is unset. readOptionalString, readOptionalBoolean, readOptionalInteger, and readOptionalJson return undefined for an unset variable instead of throwing.

    Read JSON settings through a schema, such as a Zod schema:

    ts
    import { createEnvironmentReader } from 'jg-env';
    import { z } from 'zod';
    
    const { readJson } = createEnvironmentReader(process.env);
    
    export const ALLOWED_ORIGINS = readJson('ALLOWED_ORIGINS', z.array(z.url()));

    Secret files

    jg-env/node reads secrets kept in files, such as Docker secrets. Inside a container, the secret’s variable holds the path to its file; elsewhere, the file is <KEY>.txt in a secrets directory:

    ts
    import { fileURLToPath } from 'node:url';
    
    import { createSecretReader } from 'jg-env/node';
    import { z } from 'zod';
    
    const { readSecret, readSecretJson } = createSecretReader({
      isRunningInContainer: process.env['IS_RUNNING_IN_CONTAINER'] === 'true',
      secretsDirectoryPath: fileURLToPath(new URL('../secrets/', import.meta.url)),
    });
    
    export const DATABASE_PASSWORD = readSecret('DATABASE_PASSWORD'); // as it is, without trimming
    export const ROOT_ACCOUNT = readSecretJson('ROOT_ACCOUNT', z.object({ email: z.email() }));

    Errors

    Code When
    ENV_VARIABLE_MISSING A required variable is unset
    ENV_VARIABLE_INVALID A value isn’t a string, an allowed value, true or false, a plain integer, JSON, or its schema’s
    ENV_SECRET_FILE_UNREADABLE A secret’s file is missing, unreadable, or empty

    An error’s message and data name the variable, never its value. ENV_VARIABLE_INVALID has no cause, because a JSON parser’s or a schema’s error can quote the value. Inside a container, ENV_SECRET_FILE_UNREADABLE leaves out the path and the file system’s error too, since a variable set to the secret itself by mistake would otherwise print it.

    Generated from TSDoc

    API reference

    Every export of [email protected], 8 in all, with its signature, its errors, and an example.

    jg-env

    Functionsjg-env

    createEnvironmentReader
    function

    Creates the readers for a source of environment variables. Each read looks the variable up again, so it sees the source's current value. A variable is unset when the source doesn't hold it, or holds an empty or blank string; any other value that isn't a string is invalid. Errors name the variable and never its value.

    Signature
    createEnvironmentReader(source: IEnvironmentSource): IEnvironmentReader

    Parameters

    sourceIEnvironmentSource

    Where the variables come from, such as process.env or Vite's import.meta.env.

    Returns

    IEnvironmentReader

    The readers.

    Example
    import { createEnvironmentReader } from 'jg-env';
    
    const { readEnum, readInteger, readString } = createEnvironmentReader({
      NODE_ENV: 'production',
      PORT: '8080',
      DATABASE_HOST: 'db.internal',
    });
    
    readString('DATABASE_HOST'); // 'db.internal'
    readInteger('PORT'); // 8080
    readEnum('NODE_ENV', ['development', 'production']); // 'production'

    Constantsjg-env

    ERRORS
    const

    the codes for every error thrown by the package

    Declaration
    const ERRORS: { ENV_VARIABLE_MISSING: "ENV_VARIABLE_MISSING"; ENV_VARIABLE_INVALID: "ENV_VARIABLE_INVALID"; ENV_SECRET_FILE_UNREADABLE: "ENV_SECRET_FILE_UNREADABLE" }

    Typesjg-env

    IEnvironmentReader
    type

    reads a source's variables, each time from its current values. A required read of an unset variable throws ENV_VARIABLE_MISSING, and a value that can't be used throws ENV_VARIABLE_INVALID, without a cause, so neither ever holds the value

    Declaration
    type IEnvironmentReader = {
        readString: (key: string) => string;
        readEnum: <const T extends string>(key: string, values: readonly T[]) => T;
        readBoolean: (key: string) => boolean;
        readInteger: (key: string) => number;
        readJson: <T>(key: string, schema: ISchema<T>) => T;
        readOptionalString: (key: string) => string | undefined;
        readOptionalBoolean: (key: string) => boolean | undefined;
        readOptionalInteger: (key: string) => number | undefined;
        readOptionalJson: <T>(key: string, schema: ISchema<T>) => T | undefined;
    };

    Members

    • readStringproperty
      (key: string) => string

      reads a variable that must be set, as it is, without trimming

    • readEnumproperty
      (key: string, values: readonly T[]) => T

      reads a variable that must be one of the allowed values

    • readBooleanproperty
      (key: string) => boolean

      reads a variable that must be exactly true or false

    • readIntegerproperty
      (key: string) => number

      reads a variable that must be an integer in plain decimal digits, such as 8080 or -2

    • readJsonproperty
      (key: string, schema: ISchema<T>) => T

      reads a variable that must be JSON its schema accepts

    • readOptionalStringproperty
      (key: string) => string | undefined

      the same as readString, but undefined when the variable is unset

    • readOptionalBooleanproperty
      (key: string) => boolean | undefined

      the same as readBoolean, but undefined when the variable is unset

    • readOptionalIntegerproperty
      (key: string) => number | undefined

      the same as readInteger, but undefined when the variable is unset

    • readOptionalJsonproperty
      (key: string, schema: ISchema<T>) => T | undefined

      the same as readJson, but undefined when the variable is unset

    IEnvironmentSource
    type

    where the variables come from, such as process.env or Vite's import.meta.env. Only string values count as set, so a source may hold other values, such as import.meta.env's booleans

    Declaration
    type IEnvironmentSource = Readonly<Record<string, unknown>>;
    ISchema
    type

    validates data and returns it typed, which a Zod schema does

    Declaration
    type ISchema<T> = {
        parse(value: unknown): T;
    };

    Members

    • parsemethod
      parse(value: unknown): T

    jg-env/node

    Functionsjg-env/node

    createSecretReader
    function

    Creates the readers for secrets kept in files, such as Docker secrets. Inside a container, each secret's variable holds the path to its file; elsewhere, the file is <KEY>.txt in the secrets directory. A file is read as it is, without trimming. Errors name the secret and never its value, and inside a container they leave out the path and the file system's error, which repeat whatever the variable holds.

    Signature
    createSecretReader(options: ISecretReaderOptions): ISecretReader

    Parameters

    optionsISecretReaderOptions

    Whether the program runs in a container, and the secrets directory.

    Returns

    ISecretReader

    The readers.

    Example
    import { createSecretReader } from 'jg-env/node';
    
    const { readSecret } = createSecretReader({
      isRunningInContainer: process.env['IS_RUNNING_IN_CONTAINER'] === 'true',
      secretsDirectoryPath: '/srv/app/secrets',
    });
    
    const databasePassword = readSecret('DATABASE_PASSWORD');

    Typesjg-env/node

    ISecretReader
    type

    reads secrets from files, at startup. A missing, unreadable, or empty file throws ENV_SECRET_FILE_UNREADABLE, and inside a container that error never names the path, since the path comes from a variable that might hold the secret itself by mistake

    Declaration
    type ISecretReader = {
        readSecret: (key: string) => string;
        readSecretJson: <T>(key: string, schema: ISchema<T>) => T;
    };

    Members

    • readSecretproperty
      (key: string) => string

      reads a secret's file, as it is, without trimming

    • readSecretJsonproperty
      (key: string, schema: ISchema<T>) => T

      reads a secret's file as JSON its schema accepts

    ISecretReaderOptions
    type

    where a secret reader finds each secret's file

    Declaration
    type ISecretReaderOptions = {
        isRunningInContainer: boolean;
        secretsDirectoryPath: string;
    };

    Members

    • isRunningInContainerproperty
      boolean

      true where each secret's variable holds the path to its file, such as /run/secrets/DB_PASSWORD in a container

    • secretsDirectoryPathproperty
      string

      the directory that holds each secret as <KEY>.txt, used outside a container