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 🙏

© 2026 – Pkg Stats / Ryan Hefner

envium-js

v1.2.0

Published

The Zero-Boilerplate & Type-Safe Environment Manager with SOLID principles.

Readme

🌍 Envium

The Zero-Boilerplate & Type-Safe Environment Manager.

Envium is the modern solution for Node.js environment variable management. Unlike traditional tools like dotenv that merely load loose strings, Envium segments, validates, strictly types, and protects your application's configuration powered by SOLID principles and robust Developer Experience (DX). It also provides event-driven watch functionality and flat destructuring support for hot-reloading in development.

CI Publish coverage npm version npm downloads license


🚀 Features

  • 📂 Group Segmentation: Organize logically using [GROUP] or <GROUP> syntax natively inside your .env files.
  • 🛡️ Differential Security: Applies Strict Immutability (writable: false, configurable: false) in Production and permissive Hot-Reloading (fs.watch with 100ms debounce) in Development.
  • 🛑 Fail-Fast Validation: Automatically checks runtime variables against a schema. The system prevents app execution if any required variable is missing or mismatched.
  • 🪄 Magic TypeScript Autocomplete: Inject your schema once and gain deep nested autocomplete (e.g. env.SERVER.PORT) powered by native TypeScript generics. Zero .d.ts boilerplate.
  • 🔄 Flat Destructuring: Destructure environment variables using flat keys within groups (e.g. const { ACCESS_KEY_ID } = env.AWS;) alongside nested access.
  • 📐 Flat Aliases: 3+-level deep nested fields get automatic flat alias keys inside their group (e.g. env.AWS.ACCESS_KEY_ID mirrors env.AWS.ACCESS.KEY.ID).
  • ☁️ Cloud Deployments Compatible: Unflattening engine maps flat CI/CD variables (like DATABASE_PORT=5432 from Vercel or AWS) into grouped env.DATABASE.PORT without changing code.
  • 🔄 Auto-Group (Dev): Automatically groups flat env vars by prefix (e.g. AWS_*<AWS>) in development mode. Triggered via config or CLI npx envium group.
  • 🛠️ Automated CLI DX: Integrated CLI for generating .env.example, Markdown documentation, validation hooks, grouping, and flattening.

📦 Installation

npm install envium-js
# or using pnpm
pnpm add envium-js

💡 Quick Start

1. Define your .env

Group your variables with [GROUP] or <GROUP> syntax:

NODE_ENV=development

<SERVER>
PORT=3000
DEBUG=true

[DATABASE]
HOST=localhost
USER=admin
[/DATABASE]

Both [GROUP] and <GROUP> are supported. Closing tags are optional.

2. Configure your Schema

// envium.config.ts
export const schema = {
  SERVER: {
    PORT: { type: 'number', required: true, default: 8080 },
    DEBUG: { type: 'boolean' }
  },
  DATABASE: {
    HOST: { type: 'string', required: true },
    USER: { type: 'string' }
  }
} as const;

Or generate it automatically: npx envium init --ts

3. Initialize Envium

import { envium } from 'envium-js';
import { schema } from './envium.config';

// Initialize once in your app's entry point
envium.init({
  schema,
  watch: process.env.NODE_ENV !== 'production'
});

4. Use anywhere

After initialization, import envium anywhere. Node.js module caching ensures the singleton is shared:

import { envium } from 'envium-js';

const connection = createConnection({
  host: envium.DATABASE.HOST,
  port: envium.SERVER.PORT
});

5. Destructuring

// Flat destructuring from groups
const { ENDPOINT, REGION, ACCESS_KEY_ID } = envium.AWS;

// Nested destructuring
const { ENDPOINT, REGION, ACCESS: { KEY: { ID } } } = envium.AWS;

// Flat alias (3+ levels deep automatically mirrored)
envium.AWS.ACCESS.KEY.ID    // nested access
envium.AWS.ACCESS_KEY_ID    // flat alias (auto-created)

🎛️ Unified Proxy API

Envium exposes a unified proxy object that serves as both the config container and the API surface:

import { envium } from 'envium-js';
// or shorthand:
import { env } from 'envium-js'; // backward-compatible alias

Methods

| Method | Description | |--------|-------------| | envium.init(config?) | Initialize with optional EnviumConfig | | envium.on(event, listener) | Subscribe to events ('change', 'reload') | | envium.off(event, listener?) | Unsubscribe from events | | envium.emit(event, data?) | Emit custom events | | envium.onChange(keys, listener) | Listen for specific key changes (supports partial match) | | envium.destroy() | Cleanup: close fs.watch, remove listeners, reset state |

Read-only Properties

| Property | Type | Description | |----------|------|-------------| | envium.activeMode | 'development' \| 'production' | Current security strategy mode | | envium.isInitialized | boolean | Whether init() has been called | | envium.env | any | Reference to self for ergonomic access |

Attempting to assign envium.activeMode or envium.isInitialized throws TypeError: Cannot assign to read-only property.

Configuration

interface EnviumConfig {
  path?: string;          // Path to .env file (default: '.env' — project root)
  mode?: 'development' | 'production';  // Auto-detected from NODE_ENV
  watch?: boolean;        // Watch for .env changes in dev (default: true)
  schema?: SchemaNode;    // Type schema for validation
  autoGroup?: boolean;    // Auto-group flat vars by prefix in dev (default: false)
  envFiles?: string[];    // Additional env files to merge (see Multi-Environment section below)
}

Events

// Listen for specific key changes (supports partial match)
envium.onChange(['PORT', 'SERVER'], (event) => {
  console.log(`Changed: ${event.keys.join(', ')}`);
  // event.keys    -> Array of changed flat keys
  // event.changes -> { old, new } per key
  // event.data    -> Fully updated environment tree
});

// Listen to all changes
envium.on('change', (event) => {
  console.log('Configuration changed:', event.keys);
});

// Cleanup
envium.destroy(); // Closes watcher, removes all listeners, resets state

⚡ Hot-Reload Engine

In development mode, Envium watches your .env file for changes using fs.watch with a 100ms debounce to coalesce multiple save events into a single reload.

When a change is detected:

  1. The file is re-parsed, re-validated against the schema, and re-merged with process.env
  2. The new state is deep-cloned using structuredClone (native Node.js API) — replacing the old shallow { ...obj } spread that could miss nested mutations
  3. The diff is computed using isDeepStrictEqual from node:util — replacing the old JSON.stringify hack that couldn't detect undefined, NaN, or objects with Date/Map/Set values
  4. If changes are detected, a change event is emitted with { keys, changes, data } — but only for keys that actually changed
envium.on('change', (event) => {
  // event.keys    -> keys that changed (only real modifications)
  // event.changes -> { old, new } per key (deep-compared)
  // event.data    -> fully updated, deep-cloned state
});

This ensures zero false positives — hitting "Save" without edits doesn't trigger anything — and accurate diffs even for complex nested structures.


🛡️ Security: process.env Isolation

Envium does not pass the entire process.env to its internal merge logic by default. The caller controls what environment variables reach the unflattening engine:

  • With schema: init() passes process.env explicitly to allow CI/CD overrides (e.g. SERVER_PORT=80 on Vercel overriding .env values).
  • Without schema: process.env is not passed, preventing system variables (npm_package_name, LS_COLORS) from leaking into the parsed result.

This eliminates entire classes of bugs where system env vars were accidentally split into nested objects.


📁 Multi-Environment File Support

Envium can load and merge multiple .env files on top of your base configuration, enabling environment-specific overrides without duplicating your entire .env.

How it works

Files are loaded after the base .env and merged using deep-merge: scalar values are overridden, nested objects are merged recursively. The merge happens before schema validation, so all overrides are validated together.

Auto-detection (default)

When envFiles is not specified, Envium auto-detects the file based on the active mode:

| Mode | Auto-detected file | Use case | |------|-------------------|----------| | development (default) | .env.local | Local overrides not committed to git | | production | .env.production | Production-specific values injected by CI/CD |

Example — Development setup:

# .env (committed to git)
PORT=3000
DATABASE_HOST=localhost

# .env.local (gitignored — local overrides)
PORT=4000
import { envium } from 'envium-js';

// Auto-loads .env.local in dev mode
envium.init();

console.log(envium.PORT);          // 4000 (overridden by .env.local)
console.log(envium.DATABASE_HOST); // 'localhost' (from base .env)

Custom envFiles array

Explicitly specify which files to load. This replaces auto-detection:

// Explicit files — no auto-detection
envium.init({
  envFiles: ['.env.staging', '.env.local']
});

Order matters — later files override earlier ones:

.env (base)  ←  .env.staging  ←  .env.local (wins)

Disable additional files

Pass an empty array to skip additional files entirely:

envium.init({
  envFiles: []  // No extra files, even if .env.local exists
});

Merge behavior with groups

Additional files support the same <GROUP> syntax as the base .env. Values within groups are deep-merged:

# .env (base)
<SERVER>
PORT=3000
DEBUG=true
</SERVER>

# .env.local (override)
<SERVER>
PORT=8080
</SERVER>
envium.SERVER.PORT   // 8080 (overridden)
envium.SERVER.DEBUG  // true (preserved from base)

Merge order summary

1. Base .env file (config.path or '.env')
2. Auto-detected file (.env.local in dev / .env.production in prod)  ← skipped if envFiles is set
3. Custom envFiles[] in declaration order                            ← only if envFiles is set
     │
     ▼
Deep-merged → Schema validated → Proxy created

Files that don't exist are silently skipped. Parse errors log a warning without crashing.


☁️ Production Native Unflattening

On platforms like Vercel, Railway, or AWS, .env files with [GROUP] syntax aren't possible (dashboards only support flat KEY=VALUE lists).

Envium handles this transparently:

# Deploy dashboard flat variables
SERVER_PORT=80
DATABASE_HOST=mongodb://production

Envium detects SERVER_PORT and maps it to envium.SERVER.PORT. Your application code remains unchanged.

Conflict resolution: When both a flat key (SERVER_PORT=3000) and a grouped key (SERVER.PORT=8080) exist, the group always wins deterministically — regardless of declaration order.


🧑‍💻 CLI Commands

# Generate initial config schema from .env
npx envium init [--ts]

# Update config schema when .env changes
npx envium update [--dry-run]

# Generate Markdown documentation
npx envium gen-docs

# Generate .env.example + documentation
npx envium gen-assets

# Validate .env against schema
npx envium check

# Auto-group flat env vars by prefix (e.g. AWS_* -> <AWS>)
npx envium group [--min 2] [--dry-run]

# Flatten grouped .env to flat format for cloud deployment
npx envium flatten [--output path] [--stdout]

📤 Exported Types

import type {
  EnvData,          // { [key: string]: EnvValue | EnvGroup }
  EnvValue,         // string | number | boolean
  EnvGroup,         // { [key: string]: EnvValue | EnvGroup }
  EnvChanges,       // { keys, changes, data }
  EnvProxy,         // Fully typed proxy interface
  EnviumConfig,     // Configuration options
  SchemaNode,       // Recursive schema definition
  FieldSpec,        // { type, required?, default?, description? }
  FieldDefinition,  // Alias for FieldSpec
  FieldType,        // 'string' | 'number' | 'boolean'
  ParseResult,      // { data, metadata }
  ParseMetadata,    // { style, keySourceMap, groups }
  InferSchema,      // Infers TS types from schema
  SchemaField,      // Infers field type with required/optional
} from 'envium-js';

📊 Quality & Coverage

| Metric | Current | Threshold | |--------|---------|-----------| | Statements | 94.8% | ≥ 80% | | Branches | 88.64% | ≥ 70% | | Functions | 95.23% | ≥ 85% | | Lines | 96.33% | ≥ 80% |

CI enforces coverage thresholds and runs npm audit on every push and release.


🔧 Development

npm run build          # Build with tsup (ESM + CJS + types)
npm test               # Run tests
npm run test:coverage  # Run tests with coverage
npm run typecheck      # TypeScript type checking
npm run lint           # Alias for typecheck

🤝 Contributing

Contributions are welcome! Envium follows SOLID principles and aims for a clean, modular architecture.

Getting Started

  1. Fork the repo and clone your fork
  2. Install dependencies: npm ci
  3. Run tests: npm test
  4. Run typecheck: npm run typecheck

Guidelines

  • Architecture: Each module should have a single responsibility (SRP). New features should extend existing interfaces (OCP), not modify them.
  • Dependencies: Zero runtime dependencies is a hard requirement. PRs introducing external npm packages will be rejected unless exceptional.
  • Testing: All new code must have tests. Coverage thresholds are enforced in CI (stmts ≥ 80%, branches ≥ 70%). Run npm run test:coverage before submitting.
  • TypeScript: Strict mode enabled. No any unless absolutely necessary. All public APIs must have typed signatures.
  • Commit style: Conventional commits preferred (feat:, fix:, refactor:, docs:, test:, chore:).
  • PR scope: Keep PRs focused. If you're fixing multiple unrelated issues, open separate PRs.

Project Structure

src/
├── core/          # Parser, Caster, Injector, ProxyFactory
├── security/      # DevStrategy, ProdStrategy (IStrategy)
├── validation/    # NativeValidator
├── generators/    # DocGenerator, TypeGen, ExampleGen
├── utils/
│   ├── flattening/  # path, merge, schema, aliases
│   └── envSerializer.ts
├── index.ts       # Unified envium proxy
└── cli.ts         # CLI entry point

📄 License

MIT © Misaint Murillo