UNPKG

@travetto/config

Version:

Configuration support

272 lines (228 loc) 11.3 kB
<!-- This file was generated by @travetto/doc and should not be modified directly --> <!-- Please modify https://github.com/travetto/travetto/tree/main/module/config/DOC.tsx and execute "npx trv doc" to rebuild --> # Configuration ## Configuration support **Install: @travetto/config** ```bash npm install @travetto/config # or yarn add @travetto/config ``` The config module provides support for loading application config on startup. Configuration values support the common [YAML](https://en.wikipedia.org/wiki/YAML) constructs as defined in [yaml](https://github.com/eemeli/yaml). Additionally, the configuration is built upon the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") module, to enforce type correctness, and allow for validation of configuration as an entrypoint into the application. Given that all [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) classes are [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19)-based classes, all the standard [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) and [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L24) functionality applies. ## Resolution The configuration information is comprised of: * configuration files - [YAML](https://en.wikipedia.org/wiki/YAML), [JSON](https://www.json.org), and basic properties file * configuration classes Config loading follows a defined resolution path, below is the order in increasing specificity (`ext` can be `yaml`, `yml`, `json`, `properties`): 1. `resources/application.<ext>` - Priority `100` - Load the default `application.<ext>` if available. 1. `resources/{role}.<ext>` - Priority `150` - Load environment specific profile configurations as defined by the values of `process.env.TRV_ROLE`. If the role is `std`, it is replaced with `local` for local development. 1. `resources/*.<ext>` - Priority `200` - Load profile specific configurations as defined by the values in `process.env.TRV_PROFILES` 1. [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L16) [ConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/types.ts#L11) - Priority `???` - These are custom config sources provided by the module, and are able to define their own priorities 1. [OverrideConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/override.ts#L9) - Priority `999` - This is for [@EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L37) overrides, and is at the top priority for all built-in config sources. By default all configuration data is inert, and will only be applied when constructing an instance of a configuration class. ### Mono Repo Support When working in a monorepo, the parent resources folder will also be searched with a lower priority than the the module's specific resources. This allows for shared-global configuration that can be overridden at the module level. The general priority is: 1. Mono-repo root 1. Module root 1. Folders for `TRV_RESOURCES`, in order ### A Complete Example A more complete example setup would look like: **Config: resources/application.yml** ```yaml --- database: host: localhost creds: user: test password: test ``` **Config: resources/production.json** ```json { "database": { "host": "prod-host-db", "creds": { "user": "admin-user" } } } ``` with environment variables **Config: Environment variables** ```properties TRV_PROFILES=production ``` At runtime the resolved config would be: **Terminal: Runtime Resolution** ```bash $ trv main doc/resolve.ts Config { sources: [ { priority: 100, source: 'file://application', detail: 'resources/application.yaml' }, { priority: 101, source: 'file://application', detail: 'module/config/doc/resources/application.yml' }, { priority: 200, source: 'file://local', detail: 'resources/local.yml' }, { priority: 300, source: 'file://production', detail: 'module/config/doc/resources/production.json' }, { priority: 999, source: 'memory://override' } ], active: { DBConfig: { host: 'prod-host-db', port: 2000, creds: { user: 'admin-user' } } } } ``` ### Standard Configuration Extension The framework provides two simple base classes that assist with existing patterns of usage to make adding in new configuration sources as easy as possible. The goal here is for the developer to either instantiate or extend these classes and produce a configuration source unique to their needs: **Code: Memory Provider** ```typescript import type { ConfigData } from '../parser/types.ts'; import type { ConfigSource, ConfigPayload } from './types.ts'; /** * Meant to be instantiated and provided as a unique config source */ export class MemoryConfigSource implements ConfigSource { #payload: ConfigPayload; constructor(key: string, data: ConfigData, priority: number = 500) { this.#payload = { data, priority, source: `memory://${key}` }; } get(): ConfigPayload { return this.#payload; } } ``` **Code: Environment JSON Provider** ```typescript import { JSONUtil } from '@travetto/runtime'; import type { ConfigSource, ConfigPayload } from './types.ts'; /** * Represents the environment mapped data as a JSON blob */ export class EnvConfigSource implements ConfigSource { #envKey: string; #priority: number; constructor(key: string, priority: number) { this.#envKey = key; this.#priority = priority; } get(): ConfigPayload | undefined { try { const data: Record<string, unknown> = JSONUtil.parseSafe(process.env[this.#envKey] || '{}'); return { data, priority: this.#priority, source: `env://${this.#envKey}` }; } catch { console.error(`env.${this.#envKey} is an invalid format`, { text: process.env[this.#envKey] }); } } } ``` ### Custom Configuration Provider In addition to files and environment variables, configuration sources can also be provided via the class itself. This is useful for reading remote configurations, or dealing with complex configuration normalization. The only caveat to this pattern, is that the these configuration sources cannot rely on the [ConfigurationService](https://github.com/travetto/travetto/tree/main/module/config/src/service.ts#L24) service for input. This means any needed configuration will need to be accessed via specific patterns. **Code: Custom Configuration Source** ```typescript import type { ConfigSource, ConfigPayload } from '@travetto/config'; import { Injectable } from '@travetto/di'; @Injectable() export class CustomConfigSource implements ConfigSource { async get(): Promise<ConfigPayload> { return { data: { user: { name: 'bob' } }, source: 'custom://override', priority: 2000 }; } } ``` ## Startup At startup, the [ConfigurationService](https://github.com/travetto/travetto/tree/main/module/config/src/service.ts#L24) service will log out all the registered configuration objects. The configuration state output is useful to determine if everything is configured properly when diagnosing runtime errors. This service will find all configurations, and output a redacted version with all secrets removed. The default pattern for secrets is `/password|private|secret/i`. More values can be added in your configuration under the path `config.secrets`. These values can either be simple strings (for exact match), or `/pattern/` to create a regular expression. ## Consuming The [ConfigurationService](https://github.com/travetto/travetto/tree/main/module/config/src/service.ts#L24) service provides injectable access to all of the loaded configuration. For simplicity, a decorator, [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) allows for classes to automatically be bound with config information on post construction via the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module. The decorator will install a `postConstruct` method if not already defined, that performs the binding of configuration. This is due to the fact that we cannot rewrite the constructor, and order of operation matters. ### Environment Variables Additionally there are times in which you may want to also support configuration via environment variables. [@EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L37) supports override configuration values when environment variables are present. The decorator takes in a namespace, of what part of the resolved configuration you want to bind to your class. Given the following class: **Code: Database config object** ```typescript import { Config, EnvVar } from '@travetto/config'; @Config('database') export class DBConfig { host: string; @EnvVar('DATABASE_PORT') port: number; creds: { user: string; password: string; }; } ``` You can see that the `DBConfig` allows for the `port` to be overridden by the `DATABASE_PORT` environment variable. **Terminal: Resolved database config** ```bash $ trv main doc/dbconfig-run.ts { message: 'Failed to construct @travetto/config:doc/dbconfig#DBConfig as validation errors have occurred', category: 'data', type: 'ValidationResultError', at: '2029-03-14T04:00:00.618Z', details: { class: '@travetto/config:doc/dbconfig#DBConfig', import: '@travetto/config/doc/dbconfig.ts', errors: [ { kind: 'required', active: true, value: undefined, message: 'port is required', path: 'port', type: undefined } ] } } ``` What you see, is that the configuration structure must be honored and the application will fail to start if the constraints do not hold true. This helps to ensure that the configuration, as input to the system, is verified and correct. By passing in the port via the environment variable, the config will construct properly, and the application will startup correctly: **Terminal: Resolved database config** ```bash $ DATABASE_PORT=200 trv main doc/dbconfig-run.ts Config { sources: [ { priority: 100, source: 'file://application', detail: 'resources/application.yaml' }, { priority: 101, source: 'file://application', detail: 'module/config/doc/resources/application.yml' }, { priority: 200, source: 'file://local', detail: 'resources/local.yml' }, { priority: 300, source: 'file://production', detail: 'module/config/doc/resources/production.json' }, { priority: 999, source: 'memory://override' } ], active: { DBConfig: { host: 'prod-host-db', port: 200, creds: { user: 'admin-user' } } } } ``` Additionally you may notice that the `password` field is missing, as it is redacted by default.