varcor
is a tool for streamlined management of config values, offering normalization and type enforcement.
To get started with varcor, install the package via npm:
npm install varcor
The Result
type is used to return values or errors throught varcor.
type ResultSuccess<T> = { success: true; value: T; };
type ResultFailure<F> = { success: false; error: F; };
type Result<T, F> = ResultSuccess<T> | ResultFailure<F>;
// Helper methods
Result.success: <T>(value: T) => ResultSuccess<T>;
Result.failure: <F>(error: F) => ResultFailure<F>;
The Variable
class is used to create an instance of a Variable validator/transformer.
The primary method of the class is parse
. This will take an optional string and return a Result of T
or a list of error strings.
parse(value?: string | undefined): Result<T, string[]>
Variables are Immutable - the follow methods will create new variables.
Marks a variable as optional. Type of variable becomes T | undefined
.optional(): Variable<T | undefined>
// type becomes string | undefined
const optionalVar = v.string().optional();
// Optional Status can be retrieved from the .isOptional property
console.log(optionalVar.isOptional);
Sets a default value for the variable if value is undefined.
.default(value: T | (() => T) ): Variable<T>
// if value is not supplied, result in default value
const defaultedVar = v.string().default('defaultValue');
// Default Value can be retrieved from the .defaultTo property
console.log(defaultedVar.defaultTo);
Sets the name of the variable
.from(name: string): Variable<T>
const namedVar = v.string().from('NAME');
// Variable Name can be retrieved from the .name property
console.log(namedVar.name)
Allows variable type unions
.else<S>(variable: Variable<S>): Variable<T | S>
// type becomes string | number
const stringOrNumber = v.string().else(v.number());
Applies a custom transformation function to the variable's value. An optional targetType can be provided for documentation purposes.
type Transformer<I, O> = (value: I) => Result<O, string[]>
.transform<S>(transform: Transformer<T, S>, type?: string): Variable<S>
// type number|undefind becomes boolean
const isOdd = v.number().optional().transform(
value => Result.success((Math.round(value || 0) % 2) === 1)
);
Transformations are particularly powerful, allowing for value conversion, additional validation and type conversion if necessary.
varcor provides a series of helper functions designed to define and enforce the types and constraints of your environment variables:
Define boolean variables, interpreting various string values ('true'
, 'false'
, '1'
, '0'
) as booleans.
import { v } from 'varcor';
const DEBUG = v.boolean();
Define numeric environment variables, with support for minimum and maximum constraints.
import { v } from 'varcor';
const PORT = v.number().min(3000).max(9000);
Similar to number variables, but specifically for integer values.
import { v } from 'varcor';
const RETRY_LIMIT = v.integer().min(1).max(5);
Define string variables, with optional validators or regex pattern matching.
import { v } from 'varcor';
const DATABASE_URL = v.string().regex(/mongodb:\/\/.+/, 'mongodb Url');
const MAIN_URL = v.string().url();
const EMAIL = v.string().email();
const UUID = v.string().uuid();
const PASSWORD = v.string().validate(value =>
value.length > 10 && value.length < 20
? Result.success(value)
: Result.failure(['must be between 10 and 20 characters.'])
)
Define enumerated string variables, ensuring the value matches one of the predefined options.
import { v } from 'varcor';
const ENVIRONMENT = v.enum().value('development').value('production').value('test');
You can use a single value enum - the v.literal(value: string)
helper function:
const RED = v.literal('RED');
Define variables for date and time, supporting a generic DateTime object, and both JavaScript Date
objects and luxon
DateTime objects.
Types:
type DateType = 'date' | 'time' | 'datetime' | 'timeTz' | 'datetimeTz';
type DateObject = {
year: number;
month: number;
day: number;
hour: number;
minute: number;
second: number;
ms: number;
tz: TimeZone;
};
// `from` defaults to DateTimeTz
v.dateobj:(from?: DateType): Variable<DateObject>
v.jsdate:(from?: DateType): Variable<Date>
v.luxdate:(from?: DateType): Variable<DateTime>
The DateType's each correspond to a ISO subset:
date
- YYYY-MM-DDtime
- HHHH:mm:SS[.LLL]datetime
- >[(T| )]tz
: Z | (+-)HH[:MM]timeTz
- []datetimeTz
- [timeTz]
Usage:
import { v } from 'varcor';
const OBJ_DATE = v.dateobj('datetimeTz')
const JS_DATE = v.jsdate('date');
const LUX_DATE = v.luxdate('time');
Parse and validate JSON formatted string variables.
Types:
type JsonValidator<T> = (data: any) => Result<T, string[]>;
v.json<T = any>(validator?: JsonValidator<T>): Variable<T>
Usage:
import { v } from 'varcor';
const CONFIG = v.json();
Leverage zod
schemas for complex JSON object validation.
import { z } from 'zod';
import { v } from 'varcor';
const MY_SCHEMA = z.object({ key: z.string() });
const CONFIG = v.tson(MY_SCHEMA);
DataObject
and DataObjectBuilder
are utilities for managing the configuration data within your applications. Here's how to use them effectively:
DataObject
is a simple key-value mapping where values are either strings or undefined
.
type DataObject = { [key: string]: string | undefined; };
The DataObjectBuilder
class provides an interface to incrementally build a DataObject
. It supports adding data from environment variables, JSON strings, .env
files, and more.
DataObjectBuilder is immutable, so each method creates a new DataObjectBuilder.
-
Creating a New DataObjectBuilder Instance
To start building a new
DataObject
, simply instantiateDataObjectBuilder
:import { DataObjectBuilder } from './DataObjectBuilder'; let builder = new DataObjectBuilder()
-
Adding DataObjects or DataObjectBuilders
Incorporate other DataObjects or DataObjectBuilders.
Signature
.data(data: DataObject | DataObjectBuilder):
Example
const dataObject: DataObject = { PORT: '8080', NAME: 'MyApp' }; const dataBuilder = v.data.new().addDataObject({ TYPE: 'Open', DATE: '2024-01-01' }) builder = builder.data(appConfig) builder = builder.data(dataBuilder);
-
Adding Environment Variables
Easily include all current environment variables into your data object:
Signature
.env();
-
Adding Data from an Object
Incorporate configuration data from a regular JavaScript object. Non-string values are automatically converted to JSON strings:
Signature
.object(data: Record<string, any>)
const appConfig = { port: 8080, name: "MyApp", features: { logging: true, debugMode: false }, }; builder = builder.object(appConfig);
-
Adding Data from
json
stringSignature
.json(data: string);
Example
const jsonString = '{"apiUrl": "https://api.example.com", "timeout": 5000}'; builder = builder.json(jsonString);
-
Adding Data from
dotenv
stringIf you have a
dotenv
formatted string containing environment variables, you can parse and add those variables:Signature
.dotenv(data: string);
Example
const envFormat = ` # Variables API_URL=http://api.example.com; TIMEOUT="5000"; ` builder = builder.dotenv(envFormat);
-
File Helpers
DataObjectBuilder
has two methods that allow importing fromjson
anddotenv
files.Signatures
.jsonFile(path: string, options: FileOptions); .dotenvFile(path: string, options: FileOptions);
Each method takes the FileOptions type
type FileOptions = { // if set, will determine whether to attempt to load file when?: boolean; // if false, will error if file doesn't exist optional?: boolean; // function to check if file exists, defaults to fs.existsSync fileExists?: (path: string) => boolean; // function to read file contents, defaults to fs.readFile readFile?: (path: string) => string; };
Example
//example builder = builder.dotenvFile('./production.env', { when: process.env.NODE_ENV === 'production' }); builder = builder.dotenvFile('./development.env', { when: process.env.NODE_ENV === 'development' });
-
Fluid Pattern
As
DataObjectBuilder
methods return a new builder instance, allowing for method chaining:const finalDataObject = new DataObjectBuilder() .env() .object(appConfig) .dotenvFile('./.env') .data({}) .toDataObject();
-
Helper Methods All methods on DataObjectBuilder are available in v.data, to allow easy initializing
v.data.new(); v.data.env(); v.data.data(...); v.data.object(...); v.data.json(...); v.data.dotenv(...); v.data.jsonFile(...); v.data.dotenvFile(...);
-
Finalizing and Retrieving the DataObject
Once you've added all your data sources, finalize the builder to get your
DataObject
:const finalDataObject: DataObject = builder.toDataObject(); console.log(finalDataObject);
- TODO