UNPKG

schema-env

Version:

Type-safe environment variable validation for Node.js using Zod schemas or custom adapters. Load .env files, expand variables, fetch async secrets, and validate process.env at startup.

195 lines (192 loc) 10.9 kB
import dotenv from 'dotenv'; import { z } from 'zod'; type DotenvExpandFunction = (config: dotenv.DotenvConfigOutput) => dotenv.DotenvConfigOutput; /** Input for schema validation, potentially holding values from all sources. */ type EnvironmentInput = Record<string, unknown>; /** Function signature for fetching secrets asynchronously. */ type SecretSourceFunction = () => Promise<Record<string, string | undefined>>; /** Standardized error format for validation failures. */ interface StandardizedValidationError { /** Path to the invalid field. */ path: (string | number)[]; /** Description of the validation failure. */ message: string; } /** Standardized result structure for validation adapters. */ type ValidationResult<TResult> = { success: true; data: TResult; } | { success: false; error: { issues: StandardizedValidationError[]; }; }; /** * Interface for validation library adapters. * Allows plugging in different validation libraries (Zod, Joi, Yup, etc.). * @template TResult The expected shape of the validated environment object. */ interface ValidatorAdapter<TResult> { /** * Validates the merged environment data. * @param data The raw, merged environment data object. * @returns A `ValidationResult` indicating success or failure. */ validate(data: EnvironmentInput): ValidationResult<TResult>; } /** * Base options common to both `createEnv` and `createEnvAsync`. * You must provide EITHER `schema` (for default Zod validation) OR `validator`. * * @template TSchema The Zod object schema type if using the default Zod validator. * @template TResult The expected type of the validated environment object. */ interface CreateEnvBaseOptions<TSchema extends z.ZodSchema | undefined, TResult> { /** * The Zod schema defining expected environment variables. * Required if a custom `validator` is NOT provided. * If provided, it MUST be a `z.object({...})`. * Mutually exclusive with `validator`. */ schema?: TSchema; /** * A custom validation adapter instance conforming to the `ValidatorAdapter` interface. * Required if `schema` is NOT provided. * If provided, the return type `TResult` must typically be specified via a * generic type argument on `createEnv`/`createEnvAsync` (e.g., `createEnv<undefined, MyType>({...})`). * Mutually exclusive with `schema`. */ validator?: ValidatorAdapter<TResult>; /** * Optional: Path or array of paths to .env files to load. * - Defaults to './.env' relative to `process.cwd()`. * - If an array is provided (e.g., `['.env.base', '.env.local']`), files are loaded sequentially, * with variables in later files overriding earlier ones. Non-string entries are ignored with a warning. * - Set to `false` to disable loading all .env files (including the environment-specific one). * * Note on Environment-Specific File: Regardless of whether a single path or an array * is provided (unless `dotEnvPath` is `false`), if `process.env.NODE_ENV` is set, * an attempt will be made to load an environment-specific file (e.g., `.env.development`) * *after* all files specified in `dotEnvPath` have been loaded. This environment-specific * file will override variables from the files specified in `dotEnvPath`. */ dotEnvPath?: string | false | string[]; /** * Optional: Enable variable expansion using `dotenv-expand`. Defaults to `false`. * Expansion is performed on the combined values from all loaded `.env` files * (including the environment-specific file if loaded) *before* merging with * `process.env` (for `createEnv`) or secrets (for `createEnvAsync`) and validation. * Variables in `process.env` or secrets are NOT expanded. */ expandVariables?: boolean; /** @internal */ _internalDotenvExpand?: DotenvExpandFunction; } /** * Options for the synchronous `createEnv` function. * Requires either `schema` (a Zod object schema) OR `validator`. * * @template TSchema The Zod object schema type if using the default Zod validator. * @template TResult The expected type of the validated environment object. Inferred if TSchema is provided. */ type CreateEnvOptions<TSchema extends z.AnyZodObject | undefined, TResult = TSchema extends z.AnyZodObject ? z.infer<TSchema> : unknown> = CreateEnvBaseOptions<TSchema, TResult>; /** * Options for the asynchronous `createEnvAsync` function. * Requires either `schema` (a Zod object schema) OR `validator`. * * @template TSchema The Zod object schema type if using the default Zod validator. * @template TResult The expected type of the validated environment object. Inferred if TSchema is provided. */ interface CreateEnvAsyncOptions<TSchema extends z.AnyZodObject | undefined, TResult = TSchema extends z.AnyZodObject ? z.infer<TSchema> : unknown> extends CreateEnvBaseOptions<TSchema, TResult> { /** * Optional: An array of functions that fetch secrets asynchronously. * Each function should return a Promise resolving to a `Record<string, string | undefined>`. * Secrets fetched here will override variables from `.env` files but be overridden by `process.env`. * Fetching errors are logged as warnings, but do not halt execution unless all sources fail. * * @example * ```js * const getSecretsFromAWS = async () => { * // Your logic to fetch from AWS Secrets Manager * return { DB_PASSWORD: 'fetchedPassword' }; * } * const getSecretsFromVault = async () => { * // Your logic to fetch from HashiCorp Vault * return { API_KEY: 'fetchedApiKey' }; * } * * createEnvAsync({ * schema, // Or validator * secretsSources: [getSecretsFromAWS, getSecretsFromVault] * }) * ``` */ secretsSources?: SecretSourceFunction[]; } /** * Validates and parses environment variables synchronously. * Supports Zod schema validation (default via `schema` option) or a custom * validation library via the `validator` option. * * Loads variables from `.env` files (specified paths, base, and environment-specific) and `process.env`. * Optionally expands variables using `dotenv-expand`. * Throws an error if validation fails, ensuring environment safety at startup. * * Use this for standard synchronous initialization. For fetching secrets from * external systems asynchronously, use `createEnvAsync`. * * You **must** provide either the `schema` option (for default Zod validation) * or the `validator` option (for custom validation), but not both. * * The final precedence order for variables is: * 1. `process.env` (Highest priority) * 2. Environment-specific file (e.g., `.env.production`) if `NODE_ENV` is set and `dotEnvPath` is not false. * 3. Files specified in `dotEnvPath` array (later files override earlier ones) / Single `dotEnvPath` file / Default `./.env` (if `dotEnvPath` is not false). * 4. Defaults defined in the validation schema/logic (Lowest priority - applied by Zod or custom adapter during validation). * * Note: Variable expansion (`expandVariables: true`) happens *after* all `.env` files (2, 3) are merged, * but *before* merging with `process.env` (1). * * @template TSchema - The Zod object schema type (`z.AnyZodObject`) if using default validation. Leave `undefined` if using `validator`. * @template TResult - The expected type of the validated environment object. Inferred from TSchema if using Zod, otherwise requires explicit specification (e.g., `createEnv<undefined, MyCustomType>({ validator: ... })`). * @param options - Configuration options. Requires either `schema` OR `validator`. * @returns {TResult} The validated environment object. * @throws {Error} If validation fails, options are invalid (e.g., both `schema` and `validator` provided, or neither), or file loading encounters critical errors. */ declare function createEnv<TSchema extends z.AnyZodObject | undefined, TResult = TSchema extends z.AnyZodObject ? z.infer<TSchema> : unknown>(options: CreateEnvOptions<TSchema, TResult>): TResult; /** * Validates and parses environment variables asynchronously. * Supports Zod schema validation (default via `schema` option) or a custom * validation library via the `validator` option. * * Loads variables from `.env` files, optional asynchronous `secretsSources`, and `process.env`. * Optionally expands variables from `.env` files using `dotenv-expand`. * Returns a Promise that resolves with the validated environment or rejects if validation fails. * * Use this when you need to fetch secrets from external systems during startup. * For purely synchronous validation, use `createEnv`. * * You **must** provide either the `schema` option (for default Zod validation) * or the `validator` option (for custom validation), but not both. * * The final precedence order for variables is: * 1. `process.env` (Highest priority) * 2. Variables fetched via `secretsSources` (Later sources override earlier ones). * 3. Environment-specific file (e.g., `.env.production`) if `NODE_ENV` is set and `dotEnvPath` is not false. * 4. Files specified in `dotEnvPath` array (later files override earlier ones) / Single `dotEnvPath` file / Default `./.env` (if `dotEnvPath` is not false). * 5. Defaults defined in the validation schema/logic (Lowest priority - applied by Zod or custom adapter during validation). * * Note: Variable expansion (`expandVariables: true`) happens *after* all `.env` files (3, 4) are merged, * but *before* merging with `secretsSources` (2) and `process.env` (1). * * @template TSchema - The Zod object schema type (`z.AnyZodObject`) if using default validation. Leave `undefined` if using `validator`. * @template TResult - The expected type of the validated environment object. Inferred from TSchema if using Zod, otherwise requires explicit specification (e.g., `createEnvAsync<undefined, MyCustomType>({ validator: ... })`). * @param options - Configuration options. Requires either `schema` OR `validator`. * @returns {Promise<TResult>} A Promise resolving to the validated environment object. * @throws {Error} If options are invalid (e.g., both `schema` and `validator` provided, or neither) (synchronous throw). * @throws {Error} If synchronous file loading encounters critical errors (synchronous throw). * @rejects {Error} If asynchronous operations or validation fail. */ declare function createEnvAsync<TSchema extends z.AnyZodObject | undefined, TResult = TSchema extends z.AnyZodObject ? z.infer<TSchema> : unknown>(options: CreateEnvAsyncOptions<TSchema, TResult>): Promise<TResult>; export { type CreateEnvAsyncOptions, type CreateEnvOptions, type SecretSourceFunction, type StandardizedValidationError, type ValidationResult, type ValidatorAdapter, createEnv, createEnvAsync };