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

@unifig/core

v1.1.3

Published

Universal, typed and validated configuration manager

Downloads

10,545

Readme

Universal, typed and validated configuration manager.

MIT Licensed NPM version Commitizen friendly codecov Build Status

Table of Contents

💡 Goal

Unifig aims to provides simple and abstract way of handling app's configuration. It allows to load configuration data from multiple sources without changing defined config template. Many templates can be defined to further organize the code eg. MainConfiguration and ModuleConfiguration.

Adapted configuration data is transformed into templates and validated via class-transformer and class-validator. Once initialized configurations can be reloaded without app restart.

Installation

npm i @unifig/core
# or
yarn add @unifig/core

Setting up Templates

Unifig centralizes configuration management in classes called templates. They are resposible for deserializing config values from sources into rich js / ts objects and validating them afterwards.

import { From, Nested } from '@unifig/core';
import { Transform } from 'class-transformer';
import { IsString, IsArray } from 'class-validator';

class DbSettings {
  @From({ key: 'DB_URL', default: 'localhost' })
  @IsString()
  url: string;

  @From('DB_PASSWORD')
  @IsString()
  password: string;

  @From('DB_RECONNECT_DELAYS')
  @Transform(({ value }) => value.split(',').map((n) => Number(n)))
  @IsArray()
  reconnectDelays: number[];
}

Nested Configuration

Allows to keep config structure organized by grouping inseparable properties and allowing reusage of them. The subtemplate itself is declared just as regular template, including option to nest further subtemplates in it.

export class AppSettings {
  @From('PORT')
  @IsInt()
  port: number;

  @Nested(() => DbSettings)
  db: DbSettings;
}

Accessing Configuration

Templates should be loaded before any other action in the application takes place. After that configuration can be accessed from any place in the app via global Config reference.

Quick Start

import { Config, PlainConfigAdapter } from '@unifig/core';

async function bootstrap() {
  const validationError = await Config.register({
    template: AppSettings,
    adapter: async () => ({
      PORT: 3000,
      DB_URL: 'localhost:5467',
      DB_PASSWORD: 'password',
      DB_RECONNECT_DELAYS: '56,98,34,72',
    }),
  });

  if (validationError) {
    console.error(validationError.message);
    process.exit(1);
  }

  const options = Config.getValues(AppSettings);
  console.log(options.port); // output: 3000
  console.log(options.db.url); // output: localhost:5467
}

bootstrap();

Values adapters

Unifig allows to easily swap config values sources or introduce new ones. Implementation of the adapter consist of class which exposes load method, which is called upon config initialization. The method should return dictionary with keys used in @From decorators in templates and underlying values.

import { ConfigAdapter, ConfigSource } from '@unifig/core';

export class CustomAdapter implements ConfigSyncAdapter {
  load(): ConfigSource {
    return {
      PORT: '3000', // will be parsed to number as declared in template
      DB_URL: 'localhost:5467',
      DB_PASSWORD: 'password',
      DB_RECONNECT_DELAYS: '56,98,34,72',
    };
  }
}
Config.registerSync({
  template: AppSettings,
  adapter: new CustomAdapter(),
});

In case of asynchronous way of loading config (like cloud remote configuration service) the adapter needs to implement ConfigAdapter interface.

import { ConfigAdapter, ConfigSource } from '@unifig/core';

export class RemoteConfigAdapter implements ConfigAdapter {
  async load(): Promise<ConfigSource> {
    return { ... };
  }
}

Such adapter requires to be used by async register method.

await Config.register({
  template: AppSettings,
  adapter: new RemoteConfigAdapter(),
});

See full list of adapters here.

Functions Adapters

Alternatively adapter can be defined as standalone sync or async function with same rules applied.

Config.registerSync({
  template: AppSettings,
  adapter: () => ({
    PORT: '3000',
    DB_URL: 'localhost:5467',
    DB_PASSWORD: 'password',
    DB_RECONNECT_DELAYS: '56,98,34,72',
  }),
});

Types Conversion

When loading configuration from predefined objects it's handy to disable the default behavior of implicit properties types conversion.

await Config.register({
  template: AppSettings,
  enableImplicitConversion: false,
  adapter: new CustomAdapter(),
});

Multiple Configurations

In case no single configuration root (AppSettings in above example), templates need to be registered separately.

await Config.register(
  { template: DbSettings, adapter: ... },
  { template: AuthSettings, adapter: ... },
  { templates: [FilesStorageSettings, CacheSettings], adapter: ... },
);

Accessing Nested Configuration

Nested config values can be accessed directly after parent initialization.

class DbConfig {
  @IsString()
  url: string;
}

class AppConfig {
  @IsInt()
  port: number;

  @Nested(() => DbConfig)
  db: DbConfig;
}

Config.registerSync({
  template: AppConfig,
  adapter: () => ({ port: 3000, db: { url: 'db://localhost:5467' } }),
});

console.log('Db url: ' + Config.getValues(DbConfig).url); // => Db url: db://localhost:5467

Inline Validation Rejection

To throw validation exception right away after encountering errors instead of returning it use

await Config.registerOrReject({ template: DbSettings, adapter: ... });

Stale Data

Upon changing application's configuration one must be usually restarted to re-fetch new values. Unifig delivers an option to reload registered configurations in real time without app's restart.

await Config.getContainer(Settings).refresh();

Validation

Template errors can be handled in various ways, usually exiting the application early to prevent unexpected behavior.

const validationError = Config.registerSync({ ... });

if (validationError) {
  console.error(validationError.message);
  process.exit(1);
}

Presenters

Message contains list of templates names that failed validation. The errors object contains details about what and why doesn't fullfil requirements. Presenters are to utilize this information in a readable manner.

Secrets

Validation report involves properties values that didn't pass validation. In some cases it's required to hide them. For such cases there is a Secret decorator.

export class DbConfigMock {
  @From('DB_URL')
  @IsString()
  url: string;

  @From('DB_PASSWORD')
  @Secret()
  @IsString()
  password: string;
}

With it applied, password value will be transformed into ****** in potential validation report.

License

This project is licensed under the MIT License - see the LICENSE file for details.