@monkee/turbo-config
v2.0.0
Published
turbo-ts-config package
Downloads
441
Readme
Turbo config
Complete configuration solution for typescript codebases.
Main features
- Yaml, env, cli config sources with priority configuration and overriding
- Typed, class-based
- JSON schema generation for yaml autocompletion and validation in editors and IDEs
- Documentation generator for yaml and env variables
- Built on the top of the mature community driven libraries:
- class-validator as validation solution
- class-transformer for type management
- dotenv for envs parsing
Example
@ConfigPrefix('app')
class AppConfig {
// Simple field
@ConfigField({ optional: true })
httpPort?: number;
// Nested config
@ConfigField({ nested: true })
db!: DbConfig;
// Sources overriding. Arrays support
@ConfigField({
arrayOf: 'strings',
yamlKey: 'services.manager.tasks',
cliKey: 'servicesList',
arraySeparator: ':',
})
tasks!: string[];
}
class DbConfig {
@ConfigField()
host!: string;
@ConfigField()
port: number = 3306;
@ConfigField()
autoReconnect = true;
}
Table of contents
Getting Started
Prerequisites
This library depends on decorators, so make sure your tsconfig.json includes this settings:
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Installation
Install via npm
npm i @monkee/turbo-config
Basic usage
import { Transform } from 'class-transformer';
import { IsNumber } from 'class-validator';
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class AppConfig {
/*
Every field with this decorator will be parsed as config field.
By default keys will be inferred from property name:
APP_PORT=3000 for env
appPort: 3000 yaml
--appPort=3000 for cli
*/
@ConfigField()
@Min(0)
@Max(65535)
appPort: number = 3000;
/*
NOTE: due reflect-metadata limitation you should specity property type
with default value. Othervise library won't infer type and the magic won't work.
Use ignoreProperties flag with no-inferrable-types eslint rule if necessary
*/
/*
There are some transforms and validations under the hood by default.
parseFloat() and isNumber() validator for numbers for example.
Its possible to disable everyting and handle it manually.
Any features of ClassValidator and ClassTransformer is available.
*/
@ConfigField({
disableDefaultDecorators: true,
})
@Transform(({ value }) => parseFloat(value))
@IsNumber()
redisPort!: number;
/*
You can override any source key
*/
@ConfigField({
envKey: 'SUPER_APP_HOST',
yamlKey: 'super.app.host',
cliKey: 'cli.app.host',
})
appHost!: string;
// Array example. Due to reflection limitation you should specify type
@ConfigField({ arrayOf: 'ints' })
intsArray: number[] = [1, 2, 3];
}
const main = async () => {
const { config } = await buildConfig(AppConfig, {
ymlFiles: ['config.yml', 'override.yml'],
});
console.log(config);
};
Documentation and json schema generators
import { generateConfigDoc, ConfigPrefix, ConfigField } from '@monkee/turbo-config';
import * as fs from 'fs/promises';
@ConfigPrefix('nest')
class AppConfig {
@ConfigField()
port!: number;
}
const main = async () => {
const { jsonSchema } = await buildConfig(AppConfig);
// save config reference to file
await generateConfigDoc(jsonSchema, {
// you can also use return value of generateConfigDoc to manually write file
writeToFile: 'CONFIG_REFERENCE.md',
});
// write json schema to file
fs.writeFile('config-schema.json', JSON.stringify(jsonSchema, null, 2), 'utf-8');
};
In your yml config file you can include generated json schema. Supported in VSCode and IDEA (Webstorm).
# yaml-language-server: $schema=config-schema.json
nest:
port: 3000
Advanced usage
Nested configs
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class Nested {
@ConfigField()
port = 3000;
@ConfigField()
autoReconnect = true;
}
class AppConfig {
@ConfigField({ nested: true })
nested!: Nested;
}
const main = async () => {
const { config } = await buildConfig(AppConfig);
console.log(config.nested);
};
Array of non-primitive types
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class Repository {
@ConfigField()
url!: string;
@ConfigField()
token!: string;
}
class AppConfig {
@ConfigField({ arrayOf: Repository })
repositories!: Repository[];
}
const main = async () => {
process.env.REPOSITORIES = 'url=first;token=someToken,url=second;token=secret';
const { config } = await buildConfig(AppConfig);
console.log(config.repositories);
};
Build options reference
// Default build options
{
sourcesPriority: [CONFIG_SOURCE.YAML, CONFIG_SOURCE.ENV, CONFIG_SOURCE.CLI],
throwOnValidationError: true,
throwIfYmlNotExist: false,
throwIfEnvFileNotExist: false,
ymlFiles: [],
envFiles: [],
loadEnvFiles: false,
classValidatorOptions: {
skipMissingProperties: false,
},
classTransformerOptions: {
exposeDefaultValues: true,
},
}
Error handling
const main = async () => {
// manually handle validation errors
const { config, validationErrors } = await buildConfig(AppConfig, {
throwOnValidationError: false,
});
throw new Error(validationErrors);
};
Authors
Vlad Boroda - Initial work - Enity