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

@phyxiusjs/config

v0.1.1

Published

Type-safe configuration management with hot reloading, multiple sources, and observability

Readme

@phyxiusjs/config

Configuration that tells you when it's wrong. Config that can't surprise you. Settings that explain themselves.

Every production failure starts with configuration. Missing env vars, wrong types, hidden defaults, different behavior between dev and prod. You don't find out until the system crashes.

Config fixes this. Explicit schemas, validated types, observable changes.

Installation

npm install @phyxiusjs/config @phyxiusjs/validate @phyxiusjs/clock @phyxiusjs/atom @phyxiusjs/fp

The Problem

Traditional configuration is a minefield:

// This is broken. You just don't know it yet.
const port = process.env.PORT || 3000;           // Is 3000 right? Who knows?
const timeout = parseInt(process.env.TIMEOUT);   // NaN if missing
const apiKey = process.env.API_KEY;              // undefined? Crash later!

if (!apiKey) {
  throw new Error("API_KEY required");           // At least crashes early...
}

Hidden defaults, missing validation, type coercion surprises. Configuration is where good systems go to die.

Config Helps You With This

Example 1 — Schema-Driven Configuration

import { createConfig } from "@phyxiusjs/config";
import { createSystemClock } from "@phyxiusjs/clock";
import { z } from "zod";  // Or any validator

const clock = createSystemClock();

// Define your schema
const schema = z.object({
  server: z.object({
    port: z.number().min(1).max(65535),
    host: z.string().default("localhost"),
    timeout: z.number().default(30000)
  }),
  database: z.object({
    url: z.string().url(),
    poolSize: z.number().min(1).max(100).default(10)
  }),
  features: z.object({
    rateLimit: z.boolean().default(true),
    maxRequests: z.number().default(100)
  })
});

// Load with explicit sources
const config = createConfig(schema, {
  sources: [
    { type: "env" },           // Environment variables (highest priority)
    { type: "file", path: ".env" },  // .env file
    { type: "defaults" }       // Schema defaults (lowest priority)
  ],
  clock
});

// Type-safe access with Result types
const port = config.get("server.port");  // Result<number, ConfigError>
match(port, {
  ok: (p) => console.log(`Starting on port ${p}`),
  err: (e) => console.error(`Config error: ${e.message}`)
});

Example 2 — dbt-Style Environment Variables

// Environment variables with double underscore nesting
// SERVER__PORT=3000
// SERVER__HOST=localhost
// DATABASE__URL=postgres://localhost/mydb
// FEATURES__RATE_LIMIT=true
// FEATURES__MAX_REQUESTS=100

const config = createConfig(schema, {
  sources: [{ type: "env" }],  // Automatically uses dbt convention
  clock
});

// Nested values are properly parsed
const host = config.get("server.host");      // "localhost"
const rateLimit = config.get("features.rateLimit");  // true (boolean)

Example 3 — Hot Reloading with Watch

const config = createConfig(schema, {
  sources: [{ type: "file", path: "config.json" }],
  watch: true,  // Enable hot reloading
  clock
});

// Subscribe to changes
config.subscribe((event) => {
  console.log(`Config updated: ${event.type}`);
  event.changes.forEach(change => {
    console.log(`  ${change.path}: ${change.oldValue} → ${change.newValue}`);
  });
});

// Config automatically reloads when file changes

Example 4 — Environment-Specific Configuration

const environment = process.env.NODE_ENV || "development";

const config = createConfig(schema, {
  sources: [
    { type: "env" },
    { type: "file", path: `config.${environment}.json` },
    { type: "defaults" }
  ],
  clock
});

// Development gets different defaults than production
const timeout = config.get("server.timeout");
// development: 5000ms (from config.development.json)
// production: 30000ms (from config.production.json)

Example 5 — Testing with Controlled Config

import { createControlledClock } from "@phyxiusjs/clock";

test("with specific config", () => {
  const clock = createControlledClock();
  
  const config = createConfig(schema, {
    sources: [{
      type: "object",
      data: {
        server: { port: 9999, host: "test.local" },
        database: { url: "sqlite::memory:" },
        features: { rateLimit: false }
      }
    }],
    clock
  });
  
  // Test with controlled configuration
  const port = config.get("server.port");
  expect(port).toEqual(ok(9999));
});

Config Does NOT Help You With This

Example 1 — Secret Management

// Not Config's job - use a secret manager:
const secrets = await secretManager.getSecrets();

Example 2 — Remote Configuration Services

// Not Config's job - use a config service client:
const remoteConfig = await configService.fetch();

Example 3 — Configuration UI/Admin Panels

// Not Config's job - build on top:
const configUI = new ConfigAdminPanel(config);

API Reference

createConfig(schema, options)

Creates a configuration instance with validation and type safety.

Parameters:

  • schema: Validation schema (Zod, Yup, Joi, or any validator)
  • options: Configuration options
    • sources: Array of configuration sources (order matters - first wins)
    • clock: Clock instance for time operations
    • watch: Enable hot reloading (default: false)
    • journal: Optional journal for event logging

Returns: ConfigInstance<T> where T is inferred from schema

Configuration Sources

Environment Variables

{ type: "env", prefix?: string, convention?: "dbt" | "flat" }
  • Reads from process.env
  • dbt convention: SERVER__PORTserver.port
  • Optional prefix: APP_SERVER__PORT with prefix "APP_"

File

{ type: "file", path: string, format?: "json" | "yaml" | "env" }
  • Reads from file system
  • Auto-detects format from extension
  • Supports JSON, YAML, and .env files

Object

{ type: "object", data: unknown }
  • Static configuration object
  • Useful for testing and defaults

Defaults

{ type: "defaults" }
  • Uses defaults from schema
  • Always lowest priority

Config Instance Methods

get(path: string): Result<T, ConfigError>

Gets a value at the specified path. Returns Result type for safe access.

getOrDefault<T>(path: string, defaultValue: T): T

Gets a value or returns the provided default if not found or invalid.

getAll(): Result<Config, ConfigError>

Returns the entire configuration object.

reload(): Result<void, ConfigError>

Manually triggers configuration reload from sources.

subscribe(callback: (event: ConfigEvent) => void): () => void

Subscribes to configuration changes. Returns unsubscribe function.

generateExample(): string

Generates example configuration based on schema.

Environment Variable Parsing

dbt Convention (Default)

Double underscore indicates nesting:

SERVER__PORT=3000                    # server.port = 3000
DATABASE__POOL__MIN=5                # database.pool.min = 5
FEATURES__RATE_LIMIT__ENABLED=true   # features.rateLimit.enabled = true

Type Coercion

Values are automatically parsed based on content:

  • "true" / "false" → boolean
  • "123" → number
  • "12.34" → number
  • "null" → null
  • Everything else → string

Arrays

Arrays use numeric indices:

CORS__ORIGINS__0=http://localhost:3000
CORS__ORIGINS__1=https://example.com
# Becomes: cors.origins = ["http://localhost:3000", "https://example.com"]

Testing

Config is designed for testing:

describe("api endpoint", () => {
  it("handles rate limiting", () => {
    const config = createConfig(schema, {
      sources: [{
        type: "object",
        data: {
          features: { rateLimit: true, maxRequests: 5 }
        }
      }],
      clock: createControlledClock()
    });
    
    // Test with specific configuration
  });
});

Integration with Phyxius

Config uses Phyxius primitives throughout:

  • Clock: All time operations for watch functionality
  • Atom: Atomic state updates for configuration changes
  • Journal: Optional event logging for audit trails
  • FP Result: Safe access without exceptions
  • Validate: Schema validation with any library

Why Config?

Traditional configuration fails silently:

  • Missing variables discovered at runtime
  • Type mismatches cause crashes
  • Hidden defaults nobody remembers
  • Different behavior in different environments
  • No way to track configuration changes

Config makes everything explicit:

  • Schema validation at startup
  • Type-safe access throughout
  • Observable changes for hot reloading
  • Explicit source precedence
  • Result types prevent crashes

Philosophy

Config follows Phyxius principles:

  • Make complexity explicit: Show source precedence
  • Errors are values: Result types for safe access
  • Test what matters: Controllable configuration
  • Composition over configuration: Small pieces that fit together

License

MIT