The default Configurator setup is a great starting point for quickly providing configuration hooks for
typical applications. The schema API is superficially similar to that of most command-line focused libraries,
so adoption is easy. Out of the box, you get a command line parser with help text generation, environment
variable parsing, configuration file loading, and a library of field validators that cover many common needs.
Extensible Schema System
The Configurator is pre-loaded with basic primitive types and common extensions (string, number,
boolean, array, object, date, buffer), as well as many useful field validators
(e.g. $positive, $alphanum, $directory, etc.) but developers can also define their own implementations.
Custom types and validators can provide arbitrary (potentially asynchronous!) transformations from
configuration inputs to validated outputs.
Sequenced Configuration Sources
Central to the Configurator is the concept of a ConfigurationSource. Each source encapsulates the
functionality of discovering configuration field assignments from a single origin: ObjectSource understands simple
objects, EnvironmentSource reads environment variables, CommandLineSource parses command line arguments,
and so forth.
Each ConfigurationSource is loaded in sequence. Field assignments discovered later in the sequence
override assignments from earlier in the sequence.
The Configurator provides a default sequence of predefined sources, but this sequence can be changed,
and the sources individually provide options to tune their behavior. The sequence can also be extended with
additional sources, either fully custom, or provided by optional packages.
Single Source of Truth: Configuration as a Data Model
The structure of the validated output configuration object is intended to mirror an "idealized" config
file format for your application. If a schema hierarchy is created to align with the structure of the
application and its subsystems, then each subsystem's configured properties will be nested inside a
child object. This child object can then be used in isolation to safely initialize that subsystem, without
extraneous data leaking in. This reduces the "dig through a random bag of whatever" output
generated by many other configuration libraries.
Built to be Embedded
The observant reader may have noticed that the combination of features described above suggests that if
subsystems were each built as self-contained modules, each module could internally define its own
configuration schema. One could then build a system on top of Configurator that introspects these
modules, discovers their internal schemas, and initializes each modular subsystem with a validated
object containing only their requested configurables. Furthermore, those modules could be themselves
be registered as new schema types, with a type resolver that returns singleton instances of those subsystems,
allowing them to be assigned to matching configuration fields in other modules... 🤔
Good news, this exists!
ModuleManager is a separately installed library
@versionzero/module-manager, with its own
section in the documentation.
It still provides all the Configurator capabilities described in this documentation, but
with a bias towards serving applications structured as a dependency graph of modular subsystems. As a quick pitch,
ModuleManager provides embedded declarative schemas, strongly typed references, dependency injection,
and lifecycle management. It enables you to keep your configurable field definitions fully colocated with the
modules that consume them, and provides a structured approach to wiring everything together. While the Configurator
is a fine library for direct use on its own, it really shines for larger applications when embedded in an active "smart"
layer (such as ModuleManager) that understands how your functionality is partitioned.