@mocks-server/config

1.4.1 • Public • Published

Build Status Coverage Quality Gate Downloads Renovate License


Config provider

Modular configuration provider. Reads and validates configuration from:

  • Default option values
  • Configuration received programmatically
  • Configuration Files. Using Cosmiconfig
  • Environment variables
  • Command line arguments

As a summary, it also provides:

  • Automatic config validation
  • Isolated configuration namespaces
  • Objects to get/set options
  • Events when any value changes

Different namespaces can be created for each different element in the architecture. So, each different component is the unique who knows about its own options, but the user can define options for all components at a time, and using the same methods.

This module provides configuration to Mocks Server components and plugins, but it may be used anywhere else because it is fully configurable.

Usage

A brief example:

const Config = require("@mocks-server/config");

// Create config
const config = new Config({ moduleName: "mocks" });

// Create namespace
const namespace = config.addNamespace("fooNamespace");

// Create option
const option = namespace.addOption({
  name: "fooOption",
  type: "string",
  default: "foo-value",
});

// Load configuration
config.load().then(() => {
  // Read value
  console.log(option.value);
  // Set value
  option.value = "foo-new-value";
});

// Listen to onChange events
option.onChange((newValue) => {
  console.log(`Option has a new value: ${newValue}`);
});

Configuration sources

Once options and namespaces are added, and the load method is called, the library searches for values for the options in next sources, sorted from lower to higher priority:

  • Default option value. When the option is created, it can contain a default property. That value will be assigned if no other one is found.
  • Programmatic config. An object containing initial configuration can be provided to the load or init methods.
  • Configuration file. It searches for a configuration file in the process.cwd() folder (or any other folders using built-in options). Cosmiconfig is used to provide this feature, so it is compatible with next files formats:
    • A [moduleName] property in a package.json file
    • A .[moduleName]rc file with JSON or YAML syntax.
    • A .[moduleName]rc.json, .[moduleName]rc.yaml, .[moduleName].yml, .[moduleName]rc.js, or .[moduleName]rc.cjs file.
    • A [moduleName].config.js or [moduleName].config.cjs CommonJS module exporting the object.
    • A [moduleName].config.js or [moduleName].config.cjs CommonJS module exporting a function. It receives programmatic configuration as first argument.
    • A [moduleName].config.js or [moduleName].config.cjs CommonJS module exporting an async function. It receives programmatic configuration as first argument.
  • Environment variables. Environment variables can be also used to define options values.
  • Process arguments. Commander is used under the hood to get values from command line arguments and assign them to each correspondent option.

Options

Options can be added to a namespace, or to the root config object. Both config and namespaces have an addOption method for creating them. Check the API chapter for further info.

Options can be of one of next types: string, boolean, number, object or array. This library automatically converts the values from command line arguments and environment variables to the expected type when possible. If the conversion is not possible or the validation fails an error is thrown. Validation errors provide enough context to users to let them know the option that failed. This library uses ajv and better-ajv-errors for validations.

Types string, boolean, number can be nullable using the option nullable property.

Here is an example of how to add an option to the root config object, and then you have information about how the option would be set from different sources:

const config = new Config({ moduleName: "mocks" });
config.addOption({ name: "optionA", type: "string", default: "foo" });
  • Configuration from files or programmatic: The option must be defined at first level:
{
  optionA: "foo-value"
}
  • Configuration from environment variables: The moduleName option must be prepended to the option name, using "screaming snake case".
MODULE_NAME_OPTION_A="foo-value"
  • Configuration from arguments: The argument must start by "--".
--optionA="foo-value"

Namespaces

Different isolated namespaces can be created to provide configuration to different components in the architecture. For example, Mocks Server uses config namespaces in order to allow plugins to define their own options without having conflicts with core options or other plugins options.

const config = new Config({ moduleName: "moduleName" });

config
  .addNamespace("namespaceA")
  .addOption({ name: "optionA", type: "string", default: "foo" });

When a namespace is created, its name must also be used when providing the configuration to its options. Next patterns are used to define the namespace of an option:

  • Configuration from files or programmatic: The option must be defined inside an object which key corresponds to the namespace name:
{
  namespaceA: {
    optionA: "foo-value"
  }
}
  • Configuration from environment variables: The moduleName option and the namespace name must be prepended to the option name, using "screaming snake case".
MODULE_NAME_NAMESPACE_A_OPTION_A="foo-value"
  • Configuration from arguments: The namespace name must be prepended to the option name, and separated by a dot. The argument must start by "--".
--namespaceA.optionA="foo-value"

Nested namespaces

Namespaces can be nested, so they can contain also another namespaces apart of options. The previous rules about how to use namespaces when giving values to options are also valid for nested namespaces. For example:

const config = new Config({ moduleName: "mocks" });

config
  .addNamespace("namespaceA")
  .addNamespace("namespaceB")
  .addOption({ name: "optionA", type: "string", default: "foo" });
  • Configuration from files or programmatic: Namespace names must be nested properly.
{
  namespaceA: {
    namespaceB: {
      optionA: "foo-value"
    }
  }
}
  • Configuration from environment variables: All parent namespace names must be prepended:
MOCKS_NAMESPACE_A_NAMESPACE_B_OPTION_A="foo-value"
  • Configuration from arguments: All parent namespace names must be prepended and separated by a dot:
--namespaceA.namespaceB.optionA="foo-value"

Option types

String

Options of type string are not parsed in any way.

const config = new Config({ moduleName: "moduleName" });
const option = config.addOption({
  name: "optionA",
  type: "string",
});
await config.load();

Examples about how to define options of type string from sources:

  • Programmatic or configuration files: {"optionA": "foo-value"}
  • Environment variables: MODULE_NAME_OPTION_A=foo-value
  • Arguments: node myProgram.js --optionA=foo-value

Boolean

Options of type boolean are parsed when they are defined in environment variables, and they have a special format when defined in arguments:

const option = config.addOption({
  name: "optionA",
  type: "boolean",
  default: true,
});
await config.load();

Examples about how to define options of type string from sources:

  • Programmatic or configuration files: {"optionA": false}
  • Environment variables:
    • 0 and false strings will be parsed into boolean false: MODULE_NAME_OPTION_A=false, or MODULE_NAME_OPTION_A=0.
    • Any other value will be considered true: MODULE_NAME_OPTION_A=true, MODULE_NAME_OPTION_A=1 or MODULE_NAME_OPTION_A=foo.
  • Arguments:
    • If the option has default value true, then the --no- prefix added to the option name will produce the option to have a false value: node myProgram.js --no-optionA
    • If the option has a false default value, then the option name will be set it as true: node myProgram.js --optionA

Number

Options of type number are parsed when they are defined in environment variables or command line arguments.

const option = config.addOption({
  name: "optionA",
  type: "number"
});
await config.load();

Examples about how to define options of type number from sources:

  • Programmatic or configuration files: {"optionA": 5}
  • Environment variables: MODULE_NAME_OPTION_A=5, or MODULE_NAME_OPTION_A=5.65.
  • Arguments: node myProgram.js --optionA=6.78

Object

Options of type object are parsed from JSON strings when they are defined in environment variables or command line arguments. It is important to mention that objects are merged when they are defined in different sources when loading the configuration.

const option = config.addOption({
  name: "optionA",
  type: "object"
});
await config.load();

Examples about how to define options of type object from sources:

  • Programmatic or configuration files: {"optionA": { foo: "foo", foo2: ["1", 2 ]}}
  • Environment variables: MODULE_NAME_OPTION_A={"foo":"foo","foo2":["1",2]}}
  • Arguments: node myProgram.js --optionA={"foo":"foo","foo2":["1",2]}}

Array

Options of type array are parsed from JSON strings when they are defined in environment variables. For defining them in command line arguments, multiple arguments should be provided. As in the case of objects, arrays are merged when they are defined in multiple sources when loading the configuration. The result of defining it environment variables and in arguments would be both arrays concated, for example. This behavior can be disabled using the mergeArrays option of the Config class.

const option = config.addOption({
  name: "optionA",
  type: "array",
  itemsType: "string"
});
await config.load();

Examples about how to define options of type object from sources:

  • Programmatic or configuration files: {"optionA": ["foo","foo2"]}
  • Environment variables: MODULE_NAME_OPTION_A=["foo","foo2"]
  • Arguments: A commander variadic option is created under the hood to get the values, so array items have to be defined in separated arguments. Read the commander docs for further info. node myProgram.js --optionA foo foo2

The contents of the array are also converted to its correspondent type when the itemsType option is provided.

Nullable types

An option can be null when it is set as nullable. Nullable types are string, boolean and number. Types object and array can't be nullable, their value should be set to empty array or empty object instead.

const config = new Config({ moduleName: "moduleName" });
const option = config.addOption({
  name: "optionA",
  type: "string",
  nullable: true,
  default: null,
});
await config.load();

Built-in options

The library registers some options that can be used to determine the behavior of the library itself. As the rest of the configuration created by the library, these options can be set using configuration file, environment variables, command line arguments, etc. But there are some of them that can be defined only in some specific sources because they affect to reading that sources or not.

All of the built-in options are defined in the config namespace:

  • config.readFile (Boolean): Default: true. Determines whether the configuration file should be read or not. Obviously, it would be ignored if it is defined in the configuration file.
  • config.readArguments (Boolean): Default: true. Determines whether the arguments are read or not. It can be defined only using programmatic configuration.
  • config.readEnvironment (Boolean): Default: true. Determines whether the environment variables are read or not. It can be defined using programmatic configuration or command line arguments.
  • config.fileSearchPlaces (Array): Default from cosmiconfig. An array of places to search for the configuration file. It can be defined in any source, except configuration files.
  • config.fileSearchFrom (String): Default process.cwd. Start searching for the configuration file from this folder, and keep searching up in the parent directories until arriving at the config.fileSearchStop folder.
  • config.fileSearchStop (Array): Default process.cwd. Directory where the search for the configuration file will stop.
  • config.allowUnknownArguments (Boolean): Default false. When set to true, it allows to define unknown command line arguments. It can be defined in any source.

Lifecycle

Once the config instance has been created, normally you should only call to the config.load method to load all of the configuration, apply the validations and let the library start emitting events. But for more complex use cases there is available also another method: config.init.

You can call to the config.init method at any time before calling to config.load, and all configuration sources will be preloaded and values will be assigned to the options that have been already created. In this step, the validation is not executed, so you can add more namespaces or options based on some of that configuration afterwards. For example, Mocks Server first load the configuration that defines the plugins to be loaded using the init method, then it loads them and let them add their own options, and then it executes the config.load method. In this step the validation is executed, so unknown configuration properties would produce an error. Option events are not emitted until the load method has finished.

API

Config

const config = new Config({ moduleName: "mocks", mergeArrays: false });
  • Config(options). Returns a config instance.

    • options (Object): Containing any of next properties:
      • moduleName: Used as prefix for environment variables names and config file names.
      • mergeArrays: Default true. When an option is of type array or object, this option defines whether arrays with different values coming from different sources are concated or not. If not, the value defined in the source with higher priority would be used.

Config instance

  • init(programmaticConfig): Async. Read configuration and assign it to the correspondent options but do not execute validation. Allows to read partial configuration before adding more namespaces or options. Events are not still emitted.
    • programmaticConfig (Object): Optional. Initial configuration to be set in options. It overwrite option defaults, but it is overwritten by config from files, environment and arguments.
  • load(programmaticConfig): Async. Assign configuration to the correspondent options. It executes the init method internally if it was not done before.
    • programmaticConfig (Object): Optional. If init was called before, it is ignored, otherwise, it is passed to the init method.
  • addNamespace(name): Add namespace to the root. Returns a namespace instance.
    • name (String): Name for the namespace.
  • addOption(optionProperties): Equivalent to the addOption method in namespaces, but it add the option to the root. Returns an option instance.
    • optionProperties (Object): Properties defining the option. See the addOption method in namespaces for further info.
  • addOptions(optionsProperties): Add many options. Returns an array of option instances.
    • optionsProperties (Array): Array of optionProperties.
  • namespace(name): Returns the namespace instance in the root config with name equal to name.
  • option(optionName): Returns the option instances in the root config with name equal to optionName.
  • set(configuration): Set configuration properties to each correspondent namespace and options.
    • configuration (Object): Object with programmatic configuration. Levels in the object correspond to namespaces names, and last level keys correspond to option names.
  • validate(configuration, options): Allows to prevalidate a configuration before setting it, for example. It returns an object with valid and errors properties. See AJV docs for further info.
    • configuration (Object): Object with configuration. Levels in the object correspond to namespaces names, and last level keys correspond to option names.
    • options (Object): Object with extra options for validation:
      • allowAdditionalProperties (Boolean): Default false. If true, additional properties in the configuration would not produce validation errors.
  • getValidationSchema(options): Returns a validation schema compatible with AJV for validating the configuration of all nested namespaces.
    • options (Object): Object with extra options for validation:
      • allowAdditionalProperties (Boolean): Default false. If true, the validation schema will allow additional properties.
  • value: Getter returning the current values from all namespaces and options as an object. Levels in the object correspond to namespaces names, and last level keys correspond to option names. It can be also used as setter as an alias of the set method, with default options.
  • loadedFile: Getter returning the file path of the loaded configuration file. It returns null if no configuration file was loaded.
  • namespaces: Getter returning array with all root namespaces.
  • options: Getter returning array with all root options.
  • programmaticLoadedValues: Getter returning initial values from programmatic config. Useful for debugging purposes.
  • fileLoadedValues: Getter returning initial values from file config. Useful for debugging purposes.
  • envLoadedValues: Getter returning initial values from environment config. Useful for debugging purposes.
  • argsLoadedValues: Getter returning initial values from arguments. Useful for debugging purposes.

Namespace instance

const namespace = config.addNamespace("name");
  • addNamespace(name): Add another namespace to the current namespace. Returns a namespace instance.
    • name (String): Name for the namespace.
  • addOption(optionProperties): Adds an option to the namespace. Returns an option instance.
    • optionProperties (Object): Properties defining the option.
      • name (String): Name for the option.
      • description (String): Optional. Used in help, traces, etc.
      • type (String). One of string, boolean, number, array or object. Used to apply type validation when loading configuration and in option.value setter.
      • nullable (Boolean). Optional. Default is false. When true, the option value can be set to null. It is only supported in types string, number and boolean.
      • itemsType (String). Can be defined only when type is array. It must be one of string, boolean, number or object.
      • default - Optional. Default value. Its type depends on the type option.
      • extraData - (Object). Optional. Useful to store any extra data you want in the option. For example, Mocks Server uses it to define whether an option must be written when creating the configuration scaffold or not.
  • addOptions(optionsProperties): Add many options. Returns an array of option instances.
    • optionsProperties (Array): Array of optionProperties.
  • namespace(name): Returns the namespace instance in this namespace with name equal to name.
  • option(optionName): Returns the option instances in this namespace with name equal to optionName.
  • name: Getter returning the namespace name.
  • namespaces: Getter returning an array with children namespaces.
  • options: Getter returning an array object with children options.
  • set(configuration): Set configuration properties to each correspondent child namespace and options.
    • configuration (Object): Object with configuration. Levels in the object correspond to child namespaces names, and last level keys correspond to option names.
  • value: Getter returning the current values from all child namespaces and options as an object. Levels in the object correspond to namespaces names, and last level keys correspond to option names. It can be also used as setter as an alias of the set method, with default options.
  • root: Getter returning the root configuration object.

Option instance

const option = namespace.addOption("name");
const rootOption = config.addOption("name2");
  • value: Getter of the current value. It can be also used as setter as an alias of the set method, with default options..
  • set(value): Set value.
  • onChange(callback): Allows to add a listener that will be executed whenever the value changes. It only emit events after calling to the config.start method. It returns a function that removes the listener once executed.
    • callback(value) (Function): Callback to be executed whenever the option value changes. It receives the new value as first argument.
  • name: Getter returning the option name.
  • type: Getter returning the option type.
  • nullable: Getter returning whether the option is nullable or not.
  • description: Getter returning the option description.
  • extraData: Getter returning the option extra data.
  • default: Getter returning the option default value.
  • hasBeenSet: Returns true if the option value has been actively set, no matter the source or method used to set it. Otherwise returns false.

Package Sidebar

Install

npm i @mocks-server/config

Weekly Downloads

21,005

Version

1.4.1

License

MIT

Unpacked Size

79.5 kB

Total Files

14

Last publish

Collaborators

  • javierbrea