Guide

Plugins#

The library supports extending its functionality with a few types of plugins.

Custom loaders#

Although the library comes with a few loaders out of the box, you can create your own loaders to load configuration from any source you want.

A loader is an object with a name field and a load method that returns that returns a list of configuration keys with their value.

Here is an example of a custom loader that loads configuration from a TOML file:

import toml from 'toml';
import type { KeyCollection, KeyLoader, KeyLoaderContext } from '@neato/config';
 
export function tomlLoader(filePath: string): KeyLoader {
  return {
    name: 'toml', // identifies the loader
    load(ctx: KeyLoaderContext) {
      const tomlString = fs.readFileSync(filePath, 'utf-8');
      const config = toml.parse(tomlString);
 
      // this doesn't support TOML nested objects, only top level keys
      const keys: KeyCollection = Object.entries(config).map(([key, value]) => ({
        key,
        value,
      }));
      return keys;
    },
  };
}

Custom schemas#

You can also create custom schemas to validate your configuration.

To make a custom schema, you need to implement the SchemaTransformer type.

  • Implement the extract method to extract keys from your schema, those keys are used to build the final object with correct naming conventions.
  • Implement the validate method to validate the value of the final object and optionally transform it.

Here is some psuedo code for a custom schema:

import { Type, type Static } from '@sinclair/typebox'
import type { SchemaTransformer, SchemaTransformerContext } from '@neato/config';
import { ValidationError } from '@neato/config';
 
export function typeboxSchema<T>(schema: T): SchemaTransformer<Static<T>>{
  return {
    extract() {
      // TODO implement key extraction from schema
      return [{
        normalizedKey: 'SERVER__PORT', // internal normalized key
        originalKey: 'server__port', // key for the output object. Still uses double underscore for seperator
      }]
    }
    validate(ctx: SchemaTransformerContext) {
      const output = Value.Parse(schema, ctx.object);
 
      // TODO extract errors from the output
      if (!output) throw new ValidationError([{
        message: 'Invalid value',
        path: '$'
      }]);
 
      return output;
    },
  };
}

This implementation isn't complete. To see a complete implementation, check out an existing schema implementation in the source code.