npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

envapt

v4.1.0

Published

The apt way to handle env

Readme


✨ Features

  • 🔧 Automatic Type Detection - Runtime types inferred from fallback values
  • 🔗 Template Variables - ${VAR} syntax with circular reference protection
  • 🎯 Class Properties - Functional and Decorator-based configuration for class members (Decorators: TypeScript only)
  • 🏷️ Built-in & Custom Converters - Ready-to-use converters for common patterns + custom transformations
  • 🔖 Tagged Template Resolver - Tagged template literals with environment variable resolution
  • 🌍 Environment Detection - Built-in development/staging/production handling
  • 💪 Edge Case Handling - Robust validation and parsing for all scenarios
  • 🔑 Multi-Key Lookups - Provide ordered lists of env keys and Envapt will use the first value it finds
  • 🛡️ Type Safety - Full TypeScript support with proper type inference (TypeScript optional)
  • 📂 Multiple .env Files - Load from multiple sources
  • Lightweight - Minimal overhead with dotenv bundled

Table of Contents

⚙️ Essentials

🧬 API Reference

🌍 Environment & Templates

🛠 Configuration & Errors

🚀 Examples

⬆️ Back to Top


Requirements

TypeScript Users Only

  • TypeScript: >=5.8 (Only required for decorator API)
// tsconfig.json (required settings for decorators)
{
    "experimentalDecorators": true,
    "module": "esnext", // or "nodenext"
    "moduleResolution": "bundler", // or "nodenext"
    "target": "ESNext",
    "lib": ["ESNext"]
}

[!NOTE] JavaScript users can use all features except the @Envapt decorator API. The Functional API, Tagged Template Resolver, and all converters work perfectly in plain JavaScript.

⬆️ Back to Top

Quick Start

Installation

| Package Manager | Command | | --------------- | --------------------------------- | | pnpm | pnpm add envapt | | yarn | yarn add envapt | | npm | npm install envapt | | deno (jsr) | deno add jsr:@materwelon/envapt | | deno (npm) | deno add npm:envapt | | bun | bun add envapt |

Basic Usage

Step 1: Create a .env file:

APP_PORT=8443
APP_URL=http://localhost:${APP_PORT}
DATABASE_URL=postgres://localhost:5432/mydb
IS_PRODUCTION=false
MAX_CONNECTIONS=100
ALLOWED_ORIGINS=https://app.com,https://admin.com

JavaScript Example (Functional API):

import { Envapter, Converters } from 'envapt';

// Basic usage
const port = Envapter.getNumber('APP_PORT', 3000);
const url = Envapter.get('APP_URL', 'http://localhost:3000');
const isProduction = Envapter.isProduction;

console.log(`Server running on port ${port}`); // 8443
console.log(`URL: ${url}`); // "http://localhost:8443"

// Advanced converters
const corsOrigins = Envapter.getUsing('ALLOWED_ORIGINS', Converters.Array, []);
const dbConfig = Envapter.getUsing('DATABASE_CONFIG', Converters.Json, {});

// Multi-key lookup: try primary, then replica, then fall back
const dbUrl = Envapter.get(['PRIMARY_DB_URL', 'REPLICA_DB_URL'], 'sqlite://memory');
const httpPort = Envapter.getNumber(['APP_PORT', 'PORT'], 3000);

// Tagged template literals
const message = Envapter.resolve`Server ${'APP_URL'} is ready!`;
console.log(message); // "Server http://localhost:8443 is ready!"

TypeScript Example (Decorator API):

import { Envapt, Envapter, Converters } from 'envapt';

// Global app configuration (static properties)
class AppConfig extends Envapter {
    @Envapt('APP_PORT', 3000)
    static readonly port: number;

    // The Classic Syntax only works for Primitive Converters. Converters.Url is a Built-in Converter.
    @Envapt('APP_URL', { fallback: new URL('http://localhost:3000'), converter: Converters.Url })
    static readonly url: URL;

    // Prefer CLOUD_REDIS_URL but fall back to classic REDIS_URL when missing
    @Envapt(['CLOUD_REDIS_URL', 'REDIS_URL'], 'redis://localhost:6379')
    static readonly redisUrl: string;

    @Envapt('ALLOWED_ORIGINS', {
        fallback: ['http://localhost:3000'],
        converter: Converters.Array
    })
    static readonly allowedOrigins: string[];
}

// Service configuration (instance properties)
class DatabaseService {
    @Envapt('DATABASE_URL', 'sqlite://memory')
    declare readonly databaseUrl: string;

    // Will detect that '10' is a number and set the runtime type accordingly
    @Envapt('MAX_CONNECTIONS', 10)
    declare readonly maxConnections: number;

    @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
    declare readonly timeout: number; // Converts "5s" to 5000ms

    async connect() {
        console.log(`Connecting to ${this.databaseUrl}`);
        // Connection logic here
    }
}

// Usage
console.log(AppConfig.port); // 8443 (number)
console.log(AppConfig.url.href); // "http://localhost:8443"

const dbService = new DatabaseService();
await dbService.connect();

⬆️ Back to Top

API Reference

Decorator API

[!IMPORTANT] TypeScript Only: The @Envapt decorator API requires TypeScript with experimentalDecorators: true. JavaScript users should use the Functional API instead.

The @Envapt decorator can be used on both static and instance class properties:

  • Static properties: Can use for global configuration that's shared across your entire application (e.g., app port, global features, environment settings)
  • Instance properties: Can use for service-specific configuration that may vary per service or when you want the configuration tied to a specific class instance (e.g., database connections, service endpoints, per-service settings)

Important: Instance properties must be declared with declare keyword or ! assertion since they're populated by the decorator rather than set in a constructor.

Modern Syntax (Recommended)

@Envapt('ENV_VAR', { fallback?: T, converter?: EnvConverter<T> })

[!NOTE] The first argument can be a string or an ordered array of strings: @Envapt(['PRIMARY_URL', 'SECONDARY_URL'], { fallback: 'https://example.com' }). Envapt will resolve the first key that exists.

[!TIP] Generic Typing for Better IntelliSense

You can specify explicit types using generics for better type safety and IntelliSense:

// Explicit typing provides better IntelliSense for complex types
@Envapt<DatabaseConfig>('DB_CONFIG', {
  fallback: { host: 'localhost', port: 5432, ssl: false },
  converter: Converters.Json
})
static readonly dbConfig: DatabaseConfig;

Classic Syntax

@Envapt('ENV_VAR', fallback?, converter?)

Need to chain multiple keys with the classic API? Pass an array instead of a string:

@Envapt(['HOST_PRIMARY', 'HOST_SECONDARY'], 'localhost')
static readonly host: string;

Automatic Runtime Type Detection

Types are automatically inferred from fallback values.

class Config extends Envapter {
    // Static properties for global settings
    @Envapt('APP_NAME', 'MyApp') // string
    static readonly appName: string;

    @Envapt('APP_PORT', 3000) // number
    static readonly port: number;

    @Envapt('DEBUG_MODE', false) // boolean
    static readonly debugMode: boolean;

    // Instance properties for service-specific settings
    @Envapt('SMTP_HOST', 'localhost') // string
    declare readonly smtpHost: string;

    @Envapt('SMTP_PORT', 587) // number
    declare readonly smtpPort: number;

    @Envapt('SMTP_SECURE', true) // boolean
    declare readonly smtpSecure: boolean;

    sendEmail(to: string, subject: string) {
        console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
    }
}

Primitive Converters

Envapt allows using the 5 "primitive" type-like converters. These will coerce values.

[!NOTE] The runtime validator will ignore this usage, allowing type coercion for flexibility.

Valid Primitive Types: String, Number, Boolean, Symbol, and BigInt.

class Config extends Envapter {
    @Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
    static readonly portAsString: string;

    @Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
    static readonly debugMode: boolean;

    @Envapt('USER_ID', { fallback: 12345, converter: Number })
    static readonly userId: number;

    @Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
    static readonly maxSafeInt: bigint;

    @Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
    static readonly appInstance: symbol;

    // Instance properties work the same way
    @Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
    declare readonly timeout: number;

    // Type coercion example
    @Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
    declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
}

When to use primitive converters:

  • When you need explicit type coercion between incompatible types
  • When working with external systems that provide values in unexpected formats

Built-in Converters

Envapt provides many built-in converters for common patterns:

[!IMPORTANT] Use the Converters enum instead of string literals. They look better, and provide better type inference:

import { Converters } from 'envapt';
// ✅ Recommended: Use enum
@Envapt('PORT', { converter: Converters.Number, fallback: 3000 })

// ❌ Discouraged: String literals (still supported for compatibility)
@Envapt('PORT', { converter: 'number', fallback: 3000 })

Built-in converters enforce strict type validation between the converter and fallback types. The converter's expected return type must match the fallback's type.

class Config extends Envapter {
    // Basic types
    @Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
    static readonly appName: string;

    @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
    static readonly port: number;

    @Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
    static readonly productionMode: boolean;

    // Advanced types
    @Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
    static readonly corsOrigins: string[];

    @Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
    static readonly config: object;

    @Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
    static readonly apiUrl: URL;

    @Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
    static readonly timeout: number; // Converts "30s" to 30000ms

    // Instance properties work the same way
    @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
    declare readonly cacheTtl: number; // "1h" becomes 3600000ms
}

[!WARNING] These will throw runtime errors due to type mismatches:

// ❌ String converter with number fallback
@Envapt('VAR', { converter: Converters.String, fallback: 42 })

// ❌ URL converter with string fallback
@Envapt('VAR', { converter: Converters.Url, fallback: 'http://example.com' })

// ✅ Use primitive constructors for type coercion instead
@Envapt('VAR', { converter: String, fallback: 42 })

Available Built-in Converters:

| Converter | Alias | Description | | -------------------- | ----------- | -------------------------------------------------------------------- | | Converters.String | 'string' | String values | | Converters.Number | 'number' | Numeric values (integers and floats) | | Converters.Integer | 'integer' | Integer values only | | Converters.Float | 'float' | Float values only | | Converters.Boolean | 'boolean' | Boolean values (true/false, yes/no, on/off, 1/0) | | Converters.Bigint | 'bigint' | BigInt values for large integers | | Converters.Symbol | 'symbol' | Symbol values (creates symbols from string descriptions) | | Converters.Json | 'json' | JSON objects/arrays (safe parsing with fallback) | | Converters.Array | 'array' | Comma-separated string arrays | | Converters.Url | 'url' | URL objects | | Converters.Regexp | 'regexp' | Regular expressions (supports /pattern/flags syntax) | | Converters.Date | 'date' | Date objects (supports ISO strings and timestamps) | | Converters.Time | 'time' | Time values (e.g. "5s", "30m", "2h" converted to milliseconds) |

Custom Array Converters

For more control over array parsing:

[!IMPORTANT] Array converters validate that:

  1. Fallback must be an array (if provided)
  2. All fallback elements have consistent types (no mixed types like ['string', 42, true])
  3. Array converter type matches fallback element types (if type is specified)
class Config extends Envapter {
    // Basic array (comma-separated strings)
    @Envapt('TAGS', { converter: Converters.Array, fallback: [] })
    static readonly tags: string[];

    // Custom delimiter
    @Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
    declare readonly allowedMethods: string[];

    // Custom delimiter with type conversion
    @Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
    declare readonly rateLimits: number[];

    @Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
    declare readonly featureFlags: boolean[];
}

[!WARNING] These will throw runtime validation errors:

// ❌ Mixed types in fallback array
@Envapt('MIXED', { converter: Converters.Array, fallback: ['string', 42, true] })

// ❌ Array converter type doesn't match fallback elements
@Envapt('NUMS', { converter: { delimiter: ',', type: Converters.Number }, fallback: ['not', 'numbers'] })

// ❌ Non-array fallback with array converter
@Envapt('INVALID', { converter: Converters.Array, fallback: 'not-an-array' })

ArrayConverter Interface:

  • delimiter: string - The string used to split array elements
  • type?: BuiltInConverter - Optional type to convert each element to (excludes Converters.Array, Converters.Json, and Converters.Regexp)

Custom Converters

Transform environment values to any type:

class Config extends Envapter {
    @Envapt('TAGS', {
        fallback: new Set(['default']),
        converter: (raw, fallback) => {
            if (!raw) return fallback;
            return new Set(raw.split(',').map((s) => s.trim()));
        }
    })
    static readonly tags: Set<string>;

    @Envapt('NOTIFICATION_CHANNELS', {
        fallback: new Map([['email', 'enabled']]),
        converter: (raw, fallback) => {
            if (!raw) return fallback;
            const map = new Map();
            raw.split(',').forEach((pair) => {
                const [key, value] = pair.split(':');
                map.set(key?.trim(), value?.trim() || 'enabled');
            });
            return map;
        }
    })
    declare readonly channels: Map<string, string>;
}

[!TIP] Custom Validation with Error Throwing

Custom converters can throw errors for validation. The custom converter is called even when a variable is not found in the env file(s):

@Envapt<string>('API_KEY', {
  converter(raw, _fallback) {
    if (typeof raw !== 'string' || raw === '') {
      throw new Error('API_KEY is required and cannot be empty');
    }
    return raw;
  }
})
static readonly apiKey: string;

No fallback needed here because the converter throws an error if a value is not what we want it to be

Handling Missing Values

Control what happens when environment variables don't exist:

class Config extends Envapter {
    // Returns undefined if not found
    @Envapt('OPTIONAL_FEATURE', { fallback: undefined })
    static readonly optionalFeature: string | undefined;

    // Returns null if not found (no fallback provided)
    @Envapt('MISSING_CONFIG', { converter: Converters.String })
    static readonly missingConfig: string | null;

    // Uses fallback if not found
    @Envapt('DEFAULT_THEME', { fallback: 'light' })
    static readonly defaultTheme: string;

    // Instance properties work the same way
    @Envapt('LOG_FILE_PATH', { fallback: undefined })
    declare readonly logFilePath: string | undefined;
}

Functional API

For functional-style environment variable access on primitive types:

import { Envapter, Converters } from 'envapt';

// Basic type-specific getters
const str = Envapter.get('STRING_VAR', 'default');
const num = Envapter.getNumber('NUMBER_VAR', 42);
const bool = Envapter.getBoolean('BOOLEAN_VAR', false);
const bigint = Envapter.getBigInt('BIGINT_VAR', 100n);
const symbol = Envapter.getSymbol('SYMBOL_VAR', Symbol('default'));

// Advanced converter methods
const jsonData = Envapter.getUsing('CONFIG_JSON', Converters.Json);
const urlArray = Envapter.getUsing('API_URLS', { delimiter: ',', type: Converters.Url });
const customData = Envapter.getWith('RAW_DATA', (raw) => raw?.split('|').map((s) => s.trim()));

// Multi-key inputs work everywhere: Envapt will read left-to-right
const secretsHost = Envapter.get(['SECRETS_HOST', 'DEFAULT_HOST'], 'localhost');

// Instance methods (same API available)
const envapter = new Envapter();
const value = envapter.get('VAR', 'default');
const processed = envapter.getUsing('DATA', Converters.Array);

For functional-style environment variable access with converters:

import { Envapter, Converters } from 'envapt';

// Use built-in converters directly
const config = Envapter.getUsing('API_CONFIG', Converters.Json, { default: 'value' });
const urls = Envapter.getUsing('SERVICE_URLS', { delimiter: '|', type: Converters.Url });
const pgUrl = Envapter.getUsing(['PRIMARY_PG_URL', 'SECONDARY_PG_URL'], Converters.Url);

// TypeScript: Use type override for better type inference
const typedConfig = Envapter.getUsing<{ host: string; port: number; ssl: boolean }>('DATABASE_CONFIG', Converters.Json);
// typedConfig is now typed as { host: string; port: number; ssl: boolean } instead of JsonValue | undefined

// Use custom converter functions
const processedData = Envapter.getWith(
  'RAW_DATA',
  (raw, fallback) => {
    if (!raw) return fallback ?? [];
    return raw.split(',').map((item) => ({ name: item.trim(), enabled: true }));
  },
  []
);

// Instance methods work the same way
const envapter = new Envapter();
const result = envapter.getUsing('DATABASE_CONFIG', Converters.Json);

[!TIP] Type Override with getUsing

You can explicitly specify the return type for getUsing when TypeScript's inference isn't specific enough (especially useful with Converters.Json):

// Default behavior
const config = Envapter.getUsing('CONFIG', Converters.Json); // type: JsonValue | undefined (undefined because no fallback)

// Override with specific interface
interface DatabaseConfig {
    host: string;
    port: number;
    ssl: boolean;
}
const dbConfig = Envapter.getUsing<DatabaseConfig>('DB_CONFIG', Converters.Json);
// dbConfig is now properly typed as DatabaseConfig

Make sure the fallback value matches the expected type, if you use a fallback. Otherwise you'll see a TypeScript error.
This does NOT validate the type at runtime. You'll need to handle that yourself.

Converter Type Quick Reference

| Use Case | Converter Type | Example | | ----------------------- | ------------------------- | --------------------------------------------------------- | | Type coercion | Primitive constructors | converter: String | | Strict validation | Built-in converters | converter: Converters.String | | Array parsing | Built-in Array converters | converter: { delimiter: ',', type?: Converters.String } | | Complex transforms | Custom function | converter: (raw, fallback) => ... | | Functional built-in | getUsing() method | Envapter.getUsing('VAR', Converters.Json) | | Type override | getUsing<T>() method | Envapter.getUsing<MyType>('VAR', Converters.Json) | | Functional custom | getWith() method | Envapter.getWith('VAR', (raw) => transform(raw)) |

[!TIP] Use the Converters enum. They look better. Start with built-in converters, use primitive constructors when you need coercion, and custom converters for complex transforms.

Tagged Template Resolver

Envapt provides a convenient tagged template literal syntax for resolving environment variables directly in template strings:

import { Envapter } from 'envapt';

// Given these environment variables:
// API_HOST=api.example.com
// API_PORT=8080
// API_URL=https://${API_HOST}:${API_PORT}
// SERVICE_NAME=UserService

// Use tagged template literals for string interpolation
const endpoint = Envapter.resolve`Connecting to ${'SERVICE_NAME'} at ${'API_URL'}`;
// Returns: "Connecting to UserService at https://api.example.com:8080"

const logMessage = Envapter.resolve`Starting ${'SERVICE_NAME'} on port ${'API_PORT'}`;
// Returns: "Starting UserService on port 8080"

// Works with instance methods too
const envapter = new Envapter();
const status = envapter.resolve`${'SERVICE_NAME'} is running`;
// Returns: "UserService is running"

Works seamlessly with template variables in your .env file:

# Your .env file
API_HOST=api.example.com
API_PORT=8080
API_URL=https://${API_HOST}:${API_PORT}  # Template resolved first
SERVICE_NAME=UserService
const message = Envapter.resolve`Service ${'SERVICE_NAME'} endpoint: ${'API_URL'}`;
// Returns: "Service UserService endpoint: https://api.example.com:8080"

[!NOTE] Tagged template literals work with any environment variables, including those that use ${VAR} template syntax in your .env file. The template resolution happens first, then the tagged template interpolation.

⬆️ Back to Top

Environment Detection

Envapt automatically detects your environment from these variables (in order):

  1. ENVIRONMENT
  2. ENV
  3. NODE_ENV

Supported values: development, staging, production (case-sensitive)

Environment Management

import { Envapter, EnvaptEnvironment } from 'envapt';

// Check current environment
console.log(Envapter.environment); // Environment.Development (default)
console.log(Envapter.isProduction); // false
console.log(Envapter.isDevelopment); // true
console.log(Envapter.isStaging); // false

// Set environment
Envapter.environment = EnvaptEnvironment.Production;
Envapter.environment = 'staging'; // string also works

⬆️ Back to Top

Configuration

Multiple .env Files

import { resolve } from 'node:path';
import { Envapter } from 'envapt';

// Load from multiple files
Envapter.envPaths = [resolve(import.meta.dirname, '.env.local'), resolve(import.meta.dirname, '.env.production')];

// Or single file
Envapter.envPaths = resolve(import.meta.dirname, '.env.production');

// Or just don't set a path for it to default to .env at the root of your project

// Also, in CommonJS, use `__dirname` instead of `import.meta.dirname`:

Dotenv Configuration

Envapt allows you to customize dotenv behavior by setting configuration options:

import { Envapter } from 'envapt';

// Set dotenv configuration options
Envapter.dotenvConfig = {
    encoding: 'latin1', // File encoding (default: 'utf8')
    debug: true, // Enable debug logging
    override: true, // Override existing environment variables
    quiet: false, // Suppress non-error output (default: true)
    DOTENV_KEY: 'key...' // Decryption key for .env.vault files
};

// Get current configuration
console.log(Envapter.dotenvConfig);

[!NOTE] The path and processEnv options are managed internally by Envapter and cannot be set via dotenvConfig.

⬆️ Back to Top

Template Variables

Envapt supports variable interpolation with ${VARIABLE} syntax:

DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_URL=postgres://${DATABASE_HOST}:${DATABASE_PORT}/mydb

API_VERSION=v1
API_BASE=https://api.example.com
API_ENDPOINT=${API_BASE}/${API_VERSION}/users

Circular Reference Protection

CIRCULAR_A=${CIRCULAR_B}
CIRCULAR_B=${CIRCULAR_A}

Circular references are detected and preserved as-is rather than causing infinite loops.

⬆️ Back to Top

Error Handling

Envapt provides detailed error codes for better debugging and error handling:

import { EnvaptError, EnvaptErrorCodes } from 'envapt';

try {
    // This will throw an error for invalid configuration
    Envapter.dotenvConfig = { path: '.env.custom' };
} catch (error) {
    if (error instanceof EnvaptError) {
        console.log('Error code:', error.code);
        console.log('Error message:', error.message);

        // Handle specific error types
        switch (error.code) {
            case EnvaptErrorCodes.InvalidUserDefinedConfig:
                console.log('Invalid configuration provided');
                break;
            case EnvaptErrorCodes.EnvFileNotFound:
                console.log('Environment file not found');
                break;
            default:
                console.warn('Unhandled error code:', error.code);
                break;
        }
    }
}

Error Code Reference

🔧 Fallback Errors (1xx)

| Error Code | Description | | ---------------------------------------- | --------------------------------------------------------- | | InvalidFallback (101) | Invalid fallback value provided | | InvalidFallbackType (102) | Fallback value type doesn't match expected converter type | | ArrayFallbackElementTypeMismatch (103) | Array fallback contains elements of wrong type | | FallbackConverterTypeMismatch (104) | Fallback type doesn't match the specified converter |

🧪 Converter Errors (2xx)

| Error Code | Description | | --------------------------------- | ---------------------------------------------- | | InvalidArrayConverterType (201) | Invalid array converter configuration provided | | InvalidBuiltInConverter (202) | Invalid built-in converter specified | | InvalidCustomConverter (203) | Custom converter function is invalid | | InvalidConverterType (204) | Converter type is not recognized | | PrimitiveCoercionFailed (205) | Primitive type coercion failed |

📂 Environment File & Config Errors (3xx)

| Error Code | Description | | -------------------------------- | ---------------------------------------------- | | MissingDelimiter (301) | Delimiter is missing in array converter config | | InvalidUserDefinedConfig (302) | Invalid user-defined configuration provided | | EnvFilesNotFound (303) | Specified environment file doesn't exist | | InvalidKeyInput (304) | Invalid key input (not string or string array) |

⬆️ Back to Top

Advanced Examples

JavaScript

import { Envapter, Converters } from 'envapt';

// Global configuration
const config = {
    port: Envapter.getNumber('PORT', 3000),
    requestTimeout: Envapter.getUsing('REQUEST_TIMEOUT', Converters.Time, 10000), // "5s" -> 5000ms
    featureFlags: Envapter.getWith(
        'FEATURE_FLAGS',
        (raw, fallback) => {
            if (!raw) return fallback;
            return new Set(raw.split(',').map((s) => s.trim()));
        },
        new Set(['basic'])
    )
};

// Service configuration
class DatabaseService {
    constructor() {
        this.databaseUrl = Envapter.get('DB_URL', 'sqlite://memory');
        this.cacheTtl = Envapter.getUsing('CACHE_TTL', Converters.Time, 3600000); // "1h" -> 3600000ms
        this.redisUrls = Envapter.getWith(
            'REDIS_URLS',
            (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback),
            [new URL('redis://localhost:6379')]
        );
    }

    async initialize() {
        console.log(`App running on port ${config.port}`);
        console.log(`Database: ${this.databaseUrl}`);
        console.log(`Cache TTL: ${this.cacheTtl}ms`);
    }
}

TypeScript

import { Envapt, Envapter, Converters } from 'envapt';

class AppConfig extends Envapter {
    // Global settings (static)
    @Envapt('PORT', 3000)
    static readonly port: number;

    @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 10000 })
    static readonly requestTimeout: number; // "5s" -> 5000ms (if env is set to "5s")

    @Envapt('FEATURE_FLAGS', {
        fallback: new Set(['basic']),
        converter: (raw, fallback) => {
            if (!raw) return fallback;
            return new Set(raw.split(',').map((s) => s.trim()));
        }
    })
    static readonly featureFlags: Set<string>;

    // Service settings (instance)
    @Envapt('DB_URL', 'sqlite://memory')
    declare readonly databaseUrl: string;

    @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
    declare readonly cacheTtl: number; // "1h" -> 3600000ms

    @Envapt('REDIS_URLS', {
        fallback: [new URL('redis://localhost:6379')],
        converter: (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback)
    })
    declare readonly redisUrls: URL[];

    async initialize() {
        console.log(`App running on port ${AppConfig.port}`);
        console.log(`Database: ${this.databaseUrl}`);
        console.log(`Cache TTL: ${this.cacheTtl}ms`);
    }
}

⬆️ Back to Top