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

@eljs/config-loader

v2.0.0-beta.6

Published

Configuration discovery, loading, merging, and validation for Node.js tools.

Readme

@eljs/config-loader

简体中文

Configuration discovery, loading, merging, and validation for Node.js tools.

NPM Version License

Runtime Requirements

  • Node.js 22.14 or newer
  • ESM and CommonJS entries with TypeScript declarations

Installation

pnpm add @eljs/config-loader

Quick Start

Create app.config.json:

{
  "server": { "port": 8080 }
}

Load it with defaults:

import { ConfigLoader } from '@eljs/config-loader'

const loader = new ConfigLoader({
  defaultConfigFiles: ['app.config.ts', 'app.config.js', 'app.config.json'],
  cwd: process.cwd(),
})

const config = await loader.getConfig({
  server: { host: 'localhost', port: 3000 },
})

console.log(config.server) // { host: 'localhost', port: 8080 }
console.log(loader.mainConfigFile) // Absolute path to app.config.json

Only the first existing regular-file candidate is selected. With defaults, the result is non-null and its TypeScript shape is inferred from those defaults. Without defaults, getConfig() returns null when no configuration is found. TypeScript inference does not validate file contents.

Discovery and Merge Order

const loader = new ConfigLoader({
  defaultConfigFiles: ['app.config.ts', 'app.config.json'],
  defaultConfigExts: ['local', 'development'],
  cwd: './config',
})

If app.config.ts exists, the order is:

default configuration
→ app.config.ts
→ app.config.local.ts
→ app.config.development.ts
→ final validation

Missing environment files are skipped. Later values override earlier values; arrays are concatenated by the default deep merge. Supply merge when arrays should be replaced or another merge policy is required.

Environment suffixes are explicit strings. The loader does not load .env files or automatically interpret NODE_ENV.

API

Constructor

import type { ConfigLoaderOptions } from '@eljs/config-loader'

const options: ConfigLoaderOptions = {
  defaultConfigFiles: ['app.config.json'],
  defaultConfigExts: ['local'],
  cwd: process.cwd(),
}

| Option | Contract | | --------------------------------------- | --------------------------------------------------------------------------------------------- | | defaultConfigFiles: readonly string[] | Ordered main-file candidates; selects the first regular file | | defaultConfigExts?: readonly string[] | Environment suffixes, merged in declaration order | | cwd?: string | Relative-path base; defaults to process.cwd() | | merge?: ConfigMerge | Synchronous merge callback; must preserve the caller's expected shape without mutating inputs | | validate?: ConfigValidator | Synchronous validation or normalization after merging | | reload?: boolean | Re-evaluate an asynchronous JavaScript entry module |

constructorOptions is a read-only snapshot, including frozen copies of the candidate and suffix arrays. mainConfigFile reflects the most recent instance load; it is undefined before loading or when no candidate is found.

Instance Loading

const optionalConfig = await loader.getConfig()
const configWithDefaults = await loader.getConfig({ enabled: true })
const synchronousConfig = loader.getConfigSync({ enabled: true })

Static Loading

Static methods load and merge every supplied file in order, rather than selecting a main-file candidate. Pass absolute paths:

import { ConfigLoader, getAbsFiles } from '@eljs/config-loader'

const files = getAbsFiles(['base.config.json', 'override.config.json'])
const config = await ConfigLoader.getConfig(files, { enabled: true })
const syncConfig = ConfigLoader.getConfigSync(files, { enabled: true })

// Supply load options without defaults.
const optionalConfig = await ConfigLoader.getConfig(files, undefined, {
  reload: true,
})

The asynchronous and synchronous methods accept readonly string[]. Other static helpers are:

ConfigLoader.getMainConfigFile(files, process.cwd())
ConfigLoader.getMainConfigFileSync(files, process.cwd())
ConfigLoader.getConfigFiles('/workspace/app.config.ts', ['local', 'dev'])

The last call returns the main path followed by environment-specific paths. The suffix argument also accepts readonly string[].

Path Helpers

import { addFileExt, getAbsFiles } from '@eljs/config-loader'

addFileExt('app.config.ts', 'dev') // app.config.dev.ts
getAbsFiles(['app.config.json'], '/workspace') // ['/workspace/app.config.json']

File Formats

| Format | Extensions | Async | Sync | | ---------- | --------------- | ----- | ---- | | JavaScript | .js, .cjs | Yes | Yes | | Native ESM | .mjs | Yes | No | | TypeScript | .ts | Yes | Yes | | JSON | .json | Yes | Yes | | YAML | .yaml, .yml | Yes | Yes |

JavaScript module behavior follows the containing package's module type. Loading .mjs synchronously produces CONFIG_SYNC_FORMAT_UNSUPPORTED. TypeScript is transpiled through a short-lived CommonJS transform hook; relative .ts dependencies are supported, while TypeScript path aliases are not resolved.

Validation and Reloading

import { ConfigLoader } from '@eljs/config-loader'

const loader = new ConfigLoader({
  defaultConfigFiles: ['app.config.json'],
  validate(config, context) {
    if (
      !('server' in config) ||
      !config.server ||
      typeof config.server !== 'object'
    ) {
      throw new TypeError('server configuration is required')
    }
    console.log(context.loadedConfigFiles)
    return config
  },
})

context.configFiles contains all declared input paths. context.loadedConfigFiles contains the files that produced configuration objects and participated in merging, in merge order.

Configuration roots, defaults, and callback results must be plain objects. Nested arrays are supported, but root arrays and class instances are not. Custom merge and validate callbacks must remain synchronous and return objects compatible with the caller's declared type.

Set reload: true to re-evaluate the asynchronous JavaScript entry module. Its imported dependencies still follow Node.js module caching. Synchronous CommonJS entry loading is already fresh. Each ESM reload creates another module instance, so use reload in controlled development or refresh flows.

Errors and Execution Boundary

Missing candidates and non-file paths are skipped. Existing files that cannot be parsed or executed fail the load. Loading, merging, and validation failures use ConfigLoadError; branch on error.code rather than its message:

import { ConfigLoadError } from '@eljs/config-loader'

try {
  await loader.getConfig()
} catch (error) {
  if (error instanceof ConfigLoadError) {
    console.error(error.code, error.configFile, error.cause)
  }
  throw error
}

JavaScript and TypeScript configuration executes with the current process permissions. Only load configuration from trusted locations.

Development

From the repository root:

pnpm --filter @eljs/config-loader typecheck
pnpm --filter @eljs/config-loader test
pnpm --filter @eljs/config-loader build