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

dyflex-config

v2.0.0

Published

Configuration library with multi-source loading, directive-based merging, template interpolation, and DI integration.

Readme

dyflex-config

CI Actions Publish Actions License: MIT OSS Lifecycle npm version

Compose configuration from any source. Resolve the rest.

Load from multiple sources, in various formats, merge with fine-grained control, and resolve interpolated values.
When optionally paired with dependency injection, configuration fragments become injectable services — automatically wired and ready to use.

npm install dyflex-config

Works in Node, Deno, Bun, Electron, and browsers.
Ships as ESM and CJS.


Quick start

import { makeConfig, loadConfigFile, keyValueToConfig } from 'dyflex-config';

const defaults = {
  server: {
    host: 'localhost',
    port: 3000,
  },
  db: {
    url: '<%= config.db.host + ":" + config.db.port + "/" + config.db.name %>',
    host: 'localhost',
    port: 5432,
    name: 'myapp',
  }
};

const config = await makeConfig(defaults, {},
  await loadConfigFile('./config.json'),            // merge a JSON/JSON5/YAML file
  ['db', await loadConfigFile('.env')],              // merge .env values into config.db
  keyValueToConfig(process.argv.slice(2))            // merge command-line key=value pairs
);

console.log(config.db.url); // "prod-db.example.com:5432/myapp" (interpolated at access time)

Why dyflex-config?

| | | |---|---| | Load from anywhere | JSON, JSON5, YAML, .properties, .ini, .env, code, CLI args | | Merge with precision | Deep merge by default; directives to force-replace, conditionally replace, merge arrays by element, or remove items | | Cross-reference values | Template interpolation resolves after merging — any value can reference any other | | Type-safe defaults | Define config as a const literal, derive the type: type Config = typeof defaults | | Framework-independent | Works anywhere TypeScript or JavaScript runs | | Built for DI | Config fragments register as injectable services and auto-wire at startup |

When to use it

Good fit:

  • Config assembled from multiple sources, each providing a fragment (e.g. .env for db credentials merged into config.db)
  • Environment-specific overrides layered on shared defaults
  • Values that reference other values (connection strings, derived paths)
  • DI-driven apps where config fragments should auto-wire services

Possible overkill:

  • One config file, no interpolation, no merging, no auto wiring — JSON.parse genuinely covers it

How it works

dyflex-config follows a pipeline: load → merge → interpolate → initialize.
Each step is useful on its own, but together they can wire your entire application.

1 · Define defaults

Your default configuration is an ordinary object literal — both structure definition and development defaults:

const defaults = {
  http: { port: 3000, host: 'localhost' },
  db:   { host: 'localhost', port: 5432 }
};

The TypeScript type is derived directly: type AppConfig = typeof defaults.

2 · Load and merge overrides

Load from files, env vars, or CLI arguments. Target merges at specific sub-nodes with tuples:

const config = await makeConfig(defaults, {},
  await loadConfigFile('./config.yaml'),             // merges at the root
  ['db', await loadConfigFile('.env')],              // merges into config.db
  keyValueToConfig(['http.port=8080'])               // merges key=value pairs
);

Supported formats: JSON/JSON5 (built-in), YAML, .properties, .ini, .env (via peer deps).

3 · Interpolate

Any string value can reference others using <%= ... %> templates.
Templates resolve lazily — the full merged config is available at access time:

const defaults = {
  name: 'myapp',
  version: '1.0.0',
  release: '<%= config.name + "@" + config.version %>'
};

Built-in helpers for type conversion (templates produce strings by default):

| Helper | Returns | |:--|:--| | fn.asNum(v) | number | | fn.asBool(v) | boolean | | fn.asJs(v) | the value as-is (any type) | | fn.parseJson(v) | parsed JSON object | | fn.fromEnv(v) | process.env[v] | | fn.relTo(v) | value by relative path or symbol |

Add your own custom helpers via the evalExt option.

4 · Register and initialize

This is where the pipeline pays off.

Mark a fragment as injectable with __conf_register.
After merging and interpolation, it's handed to a registrar callback — typically binding into your DI container.

Declare an initializer with __conf_init.
Initializers run after config is fully built, receiving the DI container as context:

const defaults = {
  db: {
    __conf_register: 'db-config',
    host: 'localhost',
    port: 5432,
    password: '<%= fn.fromEnv("DB_PASSWORD") %>',
    __conf_init: {
      fn: (container) => container.bindClass(DbPool).asSingleton(),
      priority: 0
    }
  }
};

const container = new Container();
const config = await makeConfig(defaults, {
  evalCb: (key, obj) => container.bindConstant(key, obj),
  ctx: container
},
  ['db', await loadConfigFile('.env')]
);

const pool = container.get(DbPool);  // fully wired with resolved config

Adding a new service is often as simple as defining a configuration fragment. The fragment carries its structure, defaults, registration, and wiring — override files supply environment-specific values; the library handles the rest.

Initializers execute in priority order (lowest first), same-priority in parallel.


Merge directives

By default, objects are deep-merged and arrays are unioned (no duplicates). Prefix keys to control behavior:

| Prefix | Behavior | |:--|:--| | !key | Force replace — overwrites the target completely | | ~key | Conditional — only replaces if the key already exists | | %key | Element merge — deep merges arrays element-by-element | | -key | Remove — removes matching elements from target array |

{
  "!server": { "port": 8080 },         // replaces entire server object
  "~feature_flags": { "beta": true },  // only if feature_flags already exists
  "-plugins": ["deprecated-plugin"]    // removes from the plugins array
}

API

makeConfig(defaults, opts, ...overrides)

All-in-one: merge, interpolate, and initialize. Returns Promise<Config>.

Options (ConfigOpts):

| Option | Purpose | |:--|:--| | evalCb | Callback for __conf_register markers (DI binding) | | evalExt | Custom template helper functions | | onEvalError | Callback when a helper fails — return a replacement value, or throw | | ctx | Passed to initializers (typically a DI container); triggers initializer execution |

Overrides must be pre-resolved — await any async sources like loadConfigFile().

mergeConfig(dst, src, mergePoint?)

Merge src into dst. Optionally target a sub-node via mergePoint (lodash path notation).

mergeConfigs(dst, sources)

Merge multiple sources. Array elements are [mergePoint, src] tuples.

evalConfig(config, registrar?, evalExt?, onEvalError?)

Walk the config, process markers, and set up template interpolation.

loadConfigFile(filepath, opts?)

Load a local file as an object. Supports JSON/JSON5 (built-in), YAML, .properties, .ini, .env. YAML, properties, and dotenv require their respective peer dependencies. For .env files, pass previously loaded config or process.env as opts to provide expansion variables for dotenv-expand.

keyValueToConfig(pairs, sep?, delim?)

Convert key=value pairs into a nested config object. Keys are lodash property paths, so db.pool.size=10 sets a deeply nested value — useful for overriding any config property from the command line.

pkgToConfig(searchDir?, name?, version?, description?)

Extract name, version, and description from package.json or npm environment. Returns Promise.


Browser support

The package provides a browser-safe entry point via the "browser" export condition. Modern bundlers (webpack, Vite, esbuild) use it automatically. Browser builds exclude loadConfigFile and pkgToConfig (which depend on Node.js fs).

License

MIT © 2020–2026 Frank Stock