npm package: @periodic/zirconium
Description: Production-grade, enterprise-safe environment configuration engine for Node.js with strict coercion, environment scoping, and schema introspection
Author: Uday Thakur
Latest version: 1.0.0

# 🔧 Periodic Zirconium

[![npm version](https://img.shields.io/npm/v/@periodic/zirconium.svg)](https://www.npmjs.com/package/@periodic/zirconium)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)

**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

```bash
npm install @periodic/zirconium
```

Or with yarn:

```bash
yarn add @periodic/zirconium
```

Or with pnpm:

```bash
pnpm add @periodic/zirconium
```

---

## 🚀 Quick Start

```typescript
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

```typescript
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.

```typescript
// 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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
// .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:

```typescript
// ✅ 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:

```typescript
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:

```typescript
import { exportJsonSchema } from '@periodic/zirconium';

const schema = exportJsonSchema(config);
console.log(JSON.stringify(schema, null, 2));
```

**Example output:**

```json
{
  "$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

```typescript
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

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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 |

```typescript
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.

```typescript
function createConfig<T>(schema: T, options?: ConfigOptions): Resolved<T>
```

### Field Builders

```typescript
string(): StringBuilder
number(): NumberBuilder
boolean(): BooleanBuilder
enumType(values: string[]): EnumBuilder
```

### Introspection

```typescript
getSchemaMetadata(config): FieldMetadata[]
getRequiredFields(config): FieldMetadata[]
getSecretFields(config): FieldMetadata[]
exportJsonSchema(config): JSONSchema
```

### Types

```typescript
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:

```typescript
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

```bash
# 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:

- [**@periodic/iridium**](https://www.npmjs.com/package/@periodic/iridium) - Structured logging
- [**@periodic/arsenic**](https://www.npmjs.com/package/@periodic/arsenic) - Semantic runtime monitoring
- [**@periodic/obsidian**](https://www.npmjs.com/package/@periodic/obsidian) - HTTP error handling
- [**@periodic/titanium**](https://www.npmjs.com/package/@periodic/titanium) - Rate limiting
- [**@periodic/osmium**](https://www.npmjs.com/package/@periodic/osmium) - Redis caching

Build complete, production-ready APIs with the Periodic series!

---

## 📖 Documentation

- [Quick Start Guide](QUICKSTART.md)
- [Field Builder Reference](BUILDERS.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

---

## 🛠️ Production Recommendations

### Environment Variables

```bash
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:

```typescript
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:

```typescript
const config = createConfig(schema, {
  onError: (errors) => {
    Sentry.captureEvent({
      message: 'Configuration validation failed',
      extra: { errors },
    });
  },
});
```

---

## 📝 License

MIT © [Uday Thakur](LICENSE)

---

## 🙏 Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

- Code of conduct
- Development setup
- Pull request process
- Coding standards
- Architecture principles

---

## 📞 Support

- 📧 **Email:** udaythakurwork@gmail.com
- 🐛 **Issues:** [GitHub Issues](https://github.com/udaythakur7469/periodic-zirconium/issues)
- 💬 **Discussions:** [GitHub Discussions](https://github.com/udaythakur7469/periodic-zirconium/discussions)

---

## 🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!

---

**Built with ❤️ by Uday Thakur for production-grade Node.js applications**