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

@periodic/zirconium

v1.0.0

Published

Production-grade, enterprise-safe environment configuration engine for Node.js with strict coercion, environment scoping, and schema introspection

Readme

🔧 Periodic Zirconium

npm version License: MIT TypeScript

Production-grade, enterprise-safe environment configuration engine for Node.js with TypeScript support

Part of the Periodic series of Node.js packages by Uday Thakur.


💡 Why Zirconium?

Zirconium gets its name from the chemical element renowned for its exceptional resistance — to heat, corrosion, and chemical attack. Just like zirconium holds its structure under extreme conditions, this library holds your configuration together under pressure — turning missing or malformed environment variables into clear, actionable errors before they take down your app.

In engineering, zirconium is prized for nuclear applications because it is transparent to neutrons — invisible in the reactor, but structurally critical. Similarly, @periodic/zirconium works quietly at startup, invisible at runtime, yet structurally critical to every service it powers.

The name represents:

  • Resilience: Validates everything once, fails fast before harm is done
  • Precision: Strict type coercion with no silent misparses
  • Transparency: Schema introspection and JSON Schema export
  • Clarity: Explains what is wrong and where, not just that startup failed

Just as zirconium is an indispensable structural material in the right hands, @periodic/zirconium serves as the foundational configuration layer for production-grade Node.js applications.


🎯 Why Choose Zirconium?

Building robust backends requires trustworthy configuration from the very first line of code, but most solutions fall short:

  • dotenv alone reads files but does nothing to validate what's inside
  • Manual process.env checks are inconsistent, scattered, and always incomplete
  • Boolean("false") silently returns true — bugs that only surface in production
  • Missing required vars crash apps mid-request instead of at startup
  • No type safety means your IDE can't help you catch misconfigurations early

Periodic Zirconium provides the perfect solution:

Zero dependencies — Pure TypeScript configuration core
Framework-agnostic — Safe for use in both libraries and applications
Strict Type Coercion — Prevents Boolean("false") === true and similar pitfalls
Environment Scoping — Validate fields only in specific environments
Secret Masking — Automatically masks sensitive values in logs and JSON output
Deep Immutability — Frozen configuration prevents accidental mutations
Schema Introspection — Query your configuration schema at runtime
JSON Schema Export — Generate JSON Schema (Draft 2020-12) from your config
Strict Mode — Detect undeclared environment variables
Type-safe — Strict TypeScript from the ground up
No global state — No side effects on import
Production-ready — Fail fast at startup, never mid-request


📦 Installation

npm install @periodic/zirconium

Or with yarn:

yarn add @periodic/zirconium

Or with pnpm:

pnpm add @periodic/zirconium

🚀 Quick Start

import { createConfig, string, number, boolean, enumType } from '@periodic/zirconium';

// 1. Define your schema
const config = createConfig({
  server: {
    port: number().int().min(1000).max(65535).default(4000),
    env: enumType(['development', 'production', 'test']).default('development'),
  },
  database: {
    url: string().from('DATABASE_URL').required(),
    poolSize: number().int().min(1).max(50).default(10),
  },
  auth: {
    jwtSecret: string().min(32).required().secret().onlyIn('production'),
  },
  features: {
    enableRateLimit: boolean().default(true),
  },
}, {
  strict: true,
  printSummary: true,
});

// 2. Use it — fully typed, deeply frozen, secrets masked
config.server.port;       // number
config.auth.jwtSecret;    // string
config.database.url;      // string

Example validation error output:

============================================================
Invalid Environment Configuration:
============================================================

• auth.jwtSecret is required
• database.url must be at least 10 characters
• server.port must be at most 65535
• NODE_ENV must be one of: development, production, test

============================================================

🧠 Core Concepts

The createConfig Function

  • createConfig is the primary factory function
  • Validates all environment variables once at startup
  • Returns a deeply frozen, fully typed configuration object
  • This is the main entry point for all applications
  • No global state, safe for multi-tenant apps

Typical usage:

  • Application code calls createConfig() once at startup
  • Validation errors exit the process with a clear message
  • The returned object is typed, frozen, and safe to pass anywhere
  • Secrets are automatically masked in logs and JSON output
const config = createConfig({
  port: number().int().min(1000).max(65535).default(4000),
  secret: string().min(32).required().secret(),
}, {
  strict: true,
  printSummary: true,
});

The Builder Pattern

  • Field builders store metadata — no validation on definition
  • Validation happens once — at createConfig() call time
  • Builders are chainable — composable and readable
  • Environment-aware — fields can be scoped to specific environments

Design principle:

Define the schema, call createConfig, get a typed and trusted config object. Everything else is just usage.

// Schema-based routing to different validation rules
auth: {
  jwtSecret: string().min(32).secret().required().onlyIn('production'),
  devBypassKey: string().skipIn('production'),
},

✨ Features

🔒 Secret Masking

Secrets are automatically masked in JSON output and console logs:

const config = createConfig({
  apiKey: string().secret().required(),
  publicKey: string().required(),
});

console.log(JSON.stringify(config));
// { "apiKey": "************", "publicKey": "pk_live_..." }

❄️ Deep Immutability

Configuration is deeply frozen to prevent accidental mutations:

const config = createConfig({
  server: { port: number().default(3000) },
});

config.server.port = 4000;
// ❌ TypeError: Cannot assign to read only property

🎯 Environment Scoping

Validate fields only in the environments that need them:

const config = createConfig({
  // Only required in production
  jwtSecret: string().required().onlyIn('production'),
  
  // Skipped in test environment
  analytics: string().skipIn('test'),
  
  // Required in production and staging
  sslCert: string().required().onlyIn('production', 'staging'),
});

📋 Strict Mode

Detect undeclared environment variables that have no business being there:

// .env
PORT=3000
UNKNOWN_VAR=value  // Not declared in schema

const config = createConfig({
  port: number().required(),
}, { strict: true });

// Error: UNKNOWN_VAR is not declared in schema

🛡️ Strict Type Coercion

Zirconium uses strict parsers to prevent common runtime pitfalls:

// ✅ CORRECT — Zirconium boolean parsing
"true"  → true
"false" → false
"1"     → true
"0"     → false
"yes"   → Error ❌

// ❌ WRONG — JavaScript Boolean()
Boolean("false") === true  // 😱

// ✅ CORRECT — Zirconium number parsing
"42"    → 42
"3.14"  → 3.14
"abc"   → Error ❌
""      → Error ❌

// ❌ WRONG — JavaScript Number()
Number("") === 0      // 😱
Number("  ") === 0    // 😱

🔍 Schema Introspection

Query your configuration schema at runtime:

import { getSchemaMetadata, getRequiredFields, getSecretFields } from '@periodic/zirconium';

const metadata = getSchemaMetadata(config);
// [
//   {
//     path: "auth.jwtSecret",
//     type: "string",
//     required: true,
//     secret: true,
//     envKey: "JWT_SECRET",
//     onlyIn: ["production"]
//   },
//   ...
// ]

const required = getRequiredFields(config);
const secrets = getSecretFields(config);

📄 JSON Schema Export

Generate JSON Schema (Draft 2020-12) from your config for tooling, docs, or validation pipelines:

import { exportJsonSchema } from '@periodic/zirconium';

const schema = exportJsonSchema(config);
console.log(JSON.stringify(schema, null, 2));

Example output:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "server": {
      "type": "object",
      "properties": {
        "port": {
          "type": "number",
          "minimum": 1000,
          "maximum": 65535
        },
        "env": {
          "type": "string",
          "enum": ["development", "production", "test"]
        }
      },
      "required": ["port", "env"]
    }
  },
  "required": ["server"]
}

📚 Common Patterns

1. Basic Express App

import express from 'express';
import { createConfig, string, number } from '@periodic/zirconium';

const config = createConfig({
  server: {
    port: number().int().min(1000).max(65535).default(3000),
    host: string().default('0.0.0.0'),
  },
  database: {
    url: string().from('DATABASE_URL').required(),
  },
});

const app = express();
app.listen(config.server.port, config.server.host);

2. Production Configuration with Secrets

const config = createConfig({
  auth: {
    jwtSecret: string().min(32).secret().required().onlyIn('production'),
    jwtExpiry: number().default(86400),
  },
  external: {
    stripeKey: string().secret().onlyIn('production'),
    sendgridKey: string().secret().onlyIn('production'),
  },
}, {
  strict: true,
  printSummary: true,
});

3. Error Logging Integration

Send validation errors to your logging service before the app exits:

import * as Sentry from '@sentry/node';
import logger from './logger';

const config = createConfig(schema, {
  onError: (errors) => {
    Sentry.captureException(new Error('Configuration validation failed'), {
      extra: { errors },
    });

    logger.error('Invalid configuration', {
      errors: errors.map(e => ({
        field: e.path,
        message: e.message,
      })),
    });
  },
});

// Process still exits with code 1 after onError runs

4. Custom Thresholds per Environment

const isDevelopment = process.env.NODE_ENV === 'development';

const config = createConfig({
  server: {
    port: number().int().min(1000).max(65535).default(isDevelopment ? 3000 : 8080),
  },
  database: {
    poolMax: number().int().default(isDevelopment ? 5 : 25),
  },
});

5. Structured Logging Integration

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const config = createConfig(schema, {
  onError: (errors) => logger.error('config.invalid', { errors }),
});

6. Schema Introspection for Diagnostics

import { getSchemaMetadata, getRequiredFields, getSecretFields } from '@periodic/zirconium';

// Useful in health check endpoints or startup diagnostics
app.get('/health/config', (req, res) => {
  res.json({
    required: getRequiredFields(config).map(f => f.path),
    secrets: getSecretFields(config).map(f => f.path),
  });
});

7. Multi-Service Monorepo

// packages/shared/config.ts
export const sharedConfig = createConfig({
  database: {
    url: string().from('DATABASE_URL').required(),
    poolMax: number().int().default(10),
  },
});

// apps/api/config.ts
export const apiConfig = createConfig({
  server: {
    port: number().int().default(3000),
  },
  auth: {
    jwtSecret: string().min(32).secret().required().onlyIn('production'),
  },
});

8. Full Production Configuration

import { createConfig, string, number, boolean, enumType } from '@periodic/zirconium';

const isDevelopment = process.env.NODE_ENV === 'development';

export const config = createConfig({
  server: {
    port: number().int().min(1000).max(65535).default(4000),
    host: string().default('0.0.0.0'),
    env: enumType(['development', 'production', 'test']).default('development'),
  },
  database: {
    url: string().from('DATABASE_URL').required(),
    poolMin: number().int().min(0).default(2),
    poolMax: number().int().min(1).default(10),
    idleTimeout: number().default(30000),
  },
  redis: {
    url: string().from('REDIS_URL').required(),
    maxRetries: number().int().default(3),
  },
  auth: {
    jwtSecret: string().min(32).secret().required().onlyIn('production'),
    jwtExpiry: number().default(86400),
    refreshTokenExpiry: number().default(604800),
  },
  features: {
    enableRateLimit: boolean().default(true),
    enableCaching: boolean().default(true),
    enableMetrics: boolean().default(false).onlyIn('production'),
  },
  external: {
    stripeKey: string().secret().onlyIn('production'),
    sendgridKey: string().secret().onlyIn('production'),
  },
}, {
  strict: true,
  printSummary: true,
});

export default config;

🎛️ Configuration Options

createConfig Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | strict | boolean | false | Fail if undeclared env vars exist | | printSummary | boolean | false | Print formatted config summary at startup | | env | string | process.env.NODE_ENV | Custom environment override | | onError | (errors) => void | — | Callback before process exit on validation failure |

const config = createConfig(schema, {
  strict: true,
  printSummary: true,
  env: 'production',
  onError: (errors) => logger.error('config.invalid', { errors }),
});

Field Builder Options

| Method | Applies To | Description | |--------|-----------|-------------| | .required() | All | Field must be present | | .default(value) | All | Default value if not set | | .secret() | All | Mask in output and logs | | .from("VAR") | All | Custom env var name | | .onlyIn("env") | All | Only validate in specific environments | | .skipIn("env") | All | Skip validation in specific environments | | .min(n) | string, number | Minimum length or value | | .max(n) | string, number | Maximum length or value | | .int() | number | Must be an integer | | .regex(pattern) | string | Pattern validation |


📋 API Reference

createConfig(schema, options?)

Validate and return a fully typed, deeply frozen configuration object.

function createConfig<T>(schema: T, options?: ConfigOptions): Resolved<T>

Field Builders

string(): StringBuilder
number(): NumberBuilder
boolean(): BooleanBuilder
enumType(values: string[]): EnumBuilder

Introspection

getSchemaMetadata(config): FieldMetadata[]
getRequiredFields(config): FieldMetadata[]
getSecretFields(config): FieldMetadata[]
exportJsonSchema(config): JSONSchema

Types

interface ConfigOptions {
  strict?: boolean;
  printSummary?: boolean;
  env?: string;
  onError?: (errors: ConfigError[]) => void | Promise<void>;
}

interface FieldMetadata {
  path: string;
  type: 'string' | 'number' | 'boolean' | 'enum';
  required: boolean;
  secret: boolean;
  envKey: string;
  onlyIn?: string[];
  skipIn?: string[];
}

interface ConfigError {
  path: string;
  message: string;
}

🧩 Architecture

@periodic/zirconium/
├── src/
│   ├── core/                  # Validation and processing engine
│   │   ├── create-config.ts  # Main createConfig factory
│   │   ├── validator.ts      # Schema and field validation logic
│   │   ├── parsers.ts        # Strict type coercion engine
│   │   ├── freeze.ts         # Deep immutability
│   │   ├── masking.ts        # Secret value masking
│   │   └── printer.ts        # Config summary printer
│   ├── builders/              # Field builder implementations
│   │   ├── string.ts         # String builder
│   │   ├── number.ts         # Number builder
│   │   ├── boolean.ts        # Boolean builder
│   │   ├── enum.ts           # Enum builder
│   │   └── index.ts          # Re-exports all builders
│   ├── introspection/         # Schema introspection and JSON Schema export
│   │   └── index.ts          # getSchemaMetadata, getRequiredFields, exportJsonSchema
│   ├── json-schema/           # JSON Schema (Draft 2020-12) generation
│   │   └── index.ts          # exportJsonSchema
│   ├── types.ts               # TypeScript interfaces and type inference
│   └── index.ts               # Public API

Design Philosophy:

  • Core is pure TypeScript with no dependencies
  • Builders store metadata declaratively without side effects
  • Validation happens exactly once, at startup, with full context
  • Introspection exposes the schema for tooling and diagnostics
  • Easy to extend with custom field types

📈 Performance

Zirconium is optimized for startup-time validation and zero-overhead runtime access:

  • Fail fast — Validates all environment variables once at process start
  • Zero runtime cost — No validation logic runs after createConfig returns
  • Deep freeze — Prevents mutation without runtime overhead
  • Async onError — Supports async error reporting before process exit
  • No monkey-patching — Clean, predictable behavior throughout

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ Runtime re-validation after startup
❌ Watching .env files for changes
❌ Vendor-specific lock-in of any kind
❌ Built-in dotenv file loading (use dotenv for that)
❌ Magic or implicit behavior on import
❌ Configuration merging from multiple sources
❌ Metrics or tracing (use @periodic/arsenic for that)
❌ Configuration files (configure in code)

Focus on doing one thing well: strict, typed, production-safe environment configuration.


🎨 TypeScript Support

Full TypeScript support with complete type inference:

import type {
  ConfigOptions,
  FieldMetadata,
  ConfigError,
} from '@periodic/zirconium';

const config = createConfig({
  port: number().int().default(3000),
  secret: string().secret().required(),
});

// TypeScript knows the exact shape
config.port;    // number
config.secret;  // string

🧪 Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

Note: All tests achieve >80% code coverage.


🤝 Related Packages

Part of the Periodic series by Uday Thakur:

Build complete, production-ready APIs with the Periodic series!


📖 Documentation


🛠️ Production Recommendations

Environment Variables

NODE_ENV=production
DATABASE_URL=postgres://...
JWT_SECRET=a-very-long-secret-at-least-32-characters

Log Aggregation

Pair with @periodic/iridium for structured JSON output:

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { createConfig } from '@periodic/zirconium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const config = createConfig(schema, {
  onError: (errors) => logger.error('config.invalid', { errors }),
});

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Error Monitoring

Integrate with error tracking:

const config = createConfig(schema, {
  onError: (errors) => {
    Sentry.captureEvent({
      message: 'Configuration validation failed',
      extra: { errors },
    });
  },
});

📝 License

MIT © Uday Thakur


🙏 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Coding standards
  • Architecture principles

📞 Support


🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!


Built with ❤️ by Uday Thakur for production-grade Node.js applications