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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@neezer/cfg

v5.5.0

Published

[![Build Status](https://travis-ci.org/neezer/node-cfg.svg?branch=master)](https://travis-ci.org/neezer/node-cfg) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-r

Downloads

43

Readme

@neezer/cfg

Build Status semantic-release

cfg is a library to manage loading configuration into your Node JS application. There are many, many libraries that do this already, but they missed some key functionality I was after:

  • Ability to mark variables as optional based on the values of other variables
  • JSON-based schema
  • Rich Typescript config object
  • Configure using XDG-based TOML

cfg offers all of that.

Usage

config.json in your project root:

{
  "aValue": {
    "desc": "A configuration value",
    "env": "A_VALUE",
    "format": "string"
  }
}
/* given `process.env.A_VALUE === 'whatever'` */

import { cfg } from "@neezer/cfg";

// dynamically generated on NPM/yarn install
// if using in a JS project, you can skip this import
import { Config } from "@neezer/cfg/dist/config";

const config = cfg<Config>();

config.aValue === "whatever";

A note about errors

By default, cfg will log configuration errors to stderr and will exit the process with process.exit(1). You can configure this behavior using the onError configuration option.

However, be advised that if you do not properly handle errors, the resulting config object may be in a bad state. I like failing hard when this happens to prevent more cryptic errors, which is why the default is so aggressive.

Example config.json

{
  "boolean": {
    "desc": "A value that will be evaluated to `true` or `false`",
    "env": "BOOL",
    "format": "boolean"
  },
  "number": {
    "desc": "A value that will be evaluated as a Number",
    "env": "NUMBER",
    "format": "number"
  },
  "path": {
    "desc": "A value representing a file path. cfg will check that a file exists at the path given, and throw an error if it doesn't. The return value is the given string.",
    "env": "PATH",
    "format": "path"
  },
  "port": {
    "desc": "A value that will be returned as a number. It must be greater than or equal to 0 and less than or equal to 65535, and it must be an integer.",
    "env": "PORT",
    "format": "port"
  },
  "url": {
    "desc": "A value that will be evaluated as a WHATWG URL object, which will be the return value.",
    "env": "URL",
    "format": "url"
  },
  "inclusion": {
    "desc": "A value that will be tested for equality against the values provided to `format`.",
    "env": "INCLUSION",
    "format": ["one", "two", "three"]
  },
  "any": {
    "desc": "A value that does not specify `format` in one of the above cases will be evaluated as a string.",
    "env": "ANY",
    "format": "whatever"
  }
}

The following properties are optional on any config entity:

| property | description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | caseInsensitive | will downcase the input before doing any validation | | optional | will not emit an error if the value fails format validation | | requiredWhen | will mark this value as required when the value for this property is evaluated to true. Note that the target value must be format: "boolean". | | assembleFrom | only valid for URLs, provide a map to build a URL value by parts. See below |

requiredWhen Example

{
  "check-me": {
    "desc": "the check value",
    "env": "A",
    "format": "boolean"
  },
  "b": {
    "desc": "the b value",
    "env": "B",
    "format": "string",
    "requiredWhen": "check-me"
  }
}
/**
 * process.env.A === 1
 * process.env.B === 'bananas'
 */
const config = cfg();

config["check-me"] === true;
config.b === "bananas";

/**
 * process.env.A === 0
 */
const config = cfg();

config["check-me"] === false;
config.b === undefined;

/**
 * process.env.A === 1
 */
const config = cfg();

// will exit and emit the error
//
//     value at "b" cannot be undefined

assembleFrom Example

NOTE: This only affects URL values when format is set to url.

Sometimes you can't provide a connection string and have to build it up by parts, specified as individual environment variables. For those cases, you can provide the assembleFrom configuration option, which accepts a map like this:

{
  "url-in-parts": {
    "desc": "a url in parts",
    "env": "URL",
    "format": "url",
    "assembleFrom": {
      "host": "URL_HOST",
      "port": "URL_PORT",
      "protocol": "URL_PROTO",
      "username": "URL_USER",
      "password": "URL_PASS",
      "search": "URL_QUERY_PARAMS",
      "pathname": "URL_PATH"
    }
  }
}

cfg will check to see if the value provided at URL is valid, but if it is not and assembleFrom is present, it will attempt to build a URL from the parts specified in the map. The values in the map are the environment variables you want to use for each part of the URL: the keys are fixed. You can also provide a tuple of values, where the first entry is the environment variable to source from, and the second value is the default value to use if no value was sourced.

If successfull, the final result will be a URL as if you had provided a value for URL.

NOTE: No verification is done on the environment variables listed in assembleFrom before they are passed to the URL constructor internally; they're all read in as simple strings.

Test Mode (allowing partial configs)

Sometimes you want to use cfg with an incomplete configuration. In order for cfg to not throw errors about missing configuration, you'll need to use it in test mode. You can do this by invoking it like this:

cfg.test(/* same options */)

This changes the behavior of cfg to throw errors when properties are accessed and not when the configuration is parsed. Basically it makes cfg lazy instead of eager.

cfg.test will also look for an alternate configuration in config.test.json or in configTest in package.json, and will merge in with the primary configuration schema. This allows you to define variables that only exist in test environments seemlessly without polluting the production configuration schema.

Migrating from 2.x.x to 3.x.x

Automagic Config Type Definition

Previously you had to define a type to pass to cfg, along with a type guard function and an assert function.

Now that all happens automatically at package install time via a postinstall hook. You don't have to pass in the type--cfg will still work fine without it--but you can get better type safety and better autocomplete functionality in your editor if you use it.

If you need to regenerate the definition, you can manually invoke the script:

./node_modules/.bin/cfg

Why at package install time?

TypeScript does not allow inference of rich JS objects at runtime. While you're authoring your application you haven't "ran" it yet, but the compile step for the library has already "run," so there's no opportunity to provide a richer type object in a dynamic fashion. This library cannot anticipate all possible user-generated configurations, which is why the previous API put that work on you--the consumer--to provide to the library.

I always thought it sucked that the type guard and type definition was basically a copy of the config JSON this library consumes. Too much opportunity for the two to drift apart. I was also unhappy with the generic Record type of Record<string, any> since you couldn't write smarter types with that limited information and I really love my VSCode autocompletion.

So this is the happy medium. The compile process for the lib knows nothing about your schema--and thus your config type--but the TypeScript compiler for your application should know about your schema without a bunch of work on your part.

If anyone knows of a better way to implement this, I'm all :ear:s.

Configure error handling with onError

The return from cfg now only returns the config object, not the errors. Configure error handling using the onError configuration option.

Migrating from 1.x.x to 2.x.x

The arguments provided to cfg are now an object:

- cfg(givenSchemaMap, assertType)
+ cfg({ schema: givenSchemaMap, check: assertType })