@ayka/fiji
is a configuration management library that provides a flexible and type-safe way to handle application configurations. It supports environment variables, default values, and secret management.
- Overview
- Installation
- Getting Started
- Usage
- Basic Example
- Using Context for Dynamic Configuration
- Hiding Secrets
- Environment Variables
- Defining Environment Variables
- Validation and Default Values
- Loading Environment Variables
- Secret Management
- Adding Environment Sources
- Extending Configuration
- Patching Configuration
- Using
asClass
for NestJS Dependency Injection
- API Reference
- Contributing
- Acknowledgements
- License
You can install @ayka/fiji
using npm, yarn, or pnpm:
npm install @ayka/fiji
yarn add @ayka/fiji
pnpm add @ayka/fiji
To get started with @ayka/fiji
, follow these steps:
- Install the package using your preferred package manager.
- Initialize a configuration factory in your project.
- Define your configuration using a plain object or a function for dynamic configurations.
- Load environment variables and convert the configuration to a JavaScript object.
To use @ayka/fiji
in your project, you need to initialize a configuration factory. You can define your configuration using either a plain object or a function that receives a context (ctx
) for more dynamic configurations.
import * as Fiji from '@ayka/fiji';
// Initialize with a plain object
const factory = Fiji.init({
appName: 'MyApp',
port: ctx.z.port().default(3000),
debug: true,
});
// Convert the configuration to a JavaScript object
const config = factory.toJS();
console.log(config);
import * as Fiji from '@ayka/fiji';
// Initialize with a function
const factory = Fiji.init((ctx) => ({
appName: 'MyApp',
port: ctx.env('PORT', ctx.z.port().default(3000)),
debug: ctx.env('DEBUG', ctx.z.boolean().default(false)),
}));
// Load environment variables and convert to JavaScript object
factory.load();
const config = factory.toJS();
console.log(config);
You can mark certain configuration values as secrets, which will be hidden when toJS
is called with the hideSecrets
option.
import * as Fiji from '@ayka/fiji';
const factory = Fiji.init((ctx) => ({
apiKey: ctx.value('my-secret-key').secret(),
dbPassword: ctx.env('DB_PASSWORD').secret(),
}));
const config = factory.toJS({ hideSecrets: true });
console.log(config); // { apiKey: '<secret>', dbPassword: '<secret>' }
@ayka/fiji
provides robust support for environment variables, allowing you to define, validate, and use them seamlessly within your configuration. It leverages zod for schema validation and znv for parsing.
You can define environment variables using the ctx.env
method. This method allows you to specify a default value and a validation schema.
import * as Fiji from '@ayka/fiji';
const factory = Fiji.init((ctx) => ({
port: ctx.env('PORT', ctx.z.port().default(3000)),
dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url()),
}));
factory.load();
const config = factory.toJS();
console.log(config);
The ctx.env
method supports validation and default values. You can use ctx.z
to define schemas for your environment variables, ensuring they meet your application's requirements.
import * as Fiji from '@ayka/fiji';
const factory = Fiji.init((ctx) => ({
port: ctx.env('PORT', ctx.z.port().default(3000)),
dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url().nonempty()),
}));
factory.load();
const config = factory.toJS();
console.log(config);
To load environment variables, use the load
method on your configuration factory. This method reads the variables from the environment and applies any defined schemas and defaults. It also allows you to specify additional options for loading environment variables from files or other sources.
factory.load({
processEnv: process.env, // Optional: Specify a custom environment source
envFiles: ['.env', 'config.env'], // Optional: Load variables from specified files
envSources: [customEnvSource], // Optional: Load variables from additional sources
});
- processEnv: A custom environment source to use instead of the default
process.env
. - envFiles: An array of file paths to load environment variables from. The files are processed in the order they are specified.
- envSources: An array of additional environment sources to load variables from. These sources can override values from earlier sources.
The load
method processes the sources in the following order:
- Environment files specified in
envFiles
. - Additional environment sources specified in
envSources
. - The
processEnv
source, which defaults toprocess.env
if not specified.
This order allows later sources to override values from earlier ones, providing flexibility in managing your configuration.
factory.load({
envFiles: ['.env', 'config.env'],
envSources: [{ PORT: '8080' }],
});
const config = factory.toJS();
console.log(config); // Configuration with values from specified sources
Environment variables can also be marked as secrets, ensuring they are not exposed when the configuration is converted to a JavaScript object.
import * as Fiji from '@ayka/fiji';
const factory = Fiji.init((ctx) => ({
apiKey: ctx.env('API_KEY').secret(),
}));
const config = factory.toJS({ hideSecrets: true });
console.log(config); // { apiKey: '<secret>' }
@ayka/fiji
allows you to add environment sources from objects or files, providing flexibility in how you manage and load your configuration.
You can add an environment source from a plain object. This is useful for testing or when you want to override environment variables programmatically.
import * as Fiji from '@ayka/fiji';
const envSource = {
PORT: '4000',
DATABASE_URL: 'postgres://user:password@localhost:5432/mydb',
};
const factory = Fiji.init((ctx) => ({
port: ctx.env('PORT', ctx.z.port().default(3000)),
dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url()),
}));
// Add the environment source from the object
factory.addSource(envSource);
factory.load();
const config = factory.toJS();
console.log(config); // { port: 4000, dbUrl: 'postgres://user:password@localhost:5432/mydb' }
You can also add an environment source from a file. This is useful for loading environment variables from .env
files or other configuration files.
import * as Fiji from '@ayka/fiji';
import * as fs from 'fs';
import * as path from 'path';
const factory = Fiji.init((ctx) => ({
port: ctx.env('PORT', ctx.z.port().default(3000)),
dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url()),
}));
// Add the environment source from a file. Defaults to '.env' if path is not specified.
factory.addEnvFile('.env').load();
const config = factory.toJS();
console.log(config);
// This will log the configuration with values from the .env file
// For example: { port: 8080, dbUrl: 'postgres://user:password@localhost:5432/mydb' }
You can combine multiple environment sources, such as objects and files, to create a comprehensive configuration.
import * as Fiji from '@ayka/fiji';
import * as fs from 'fs';
import * as path from 'path';
const envSourceObject = {
PORT: '4000',
};
const factory = Fiji.init((ctx) => ({
port: ctx.env('PORT', ctx.z.port().default(3000)),
dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url()),
}));
// Combine multiple sources by chaining methods
factory.addSource(envSourceObject).addEnvFile('.env').addEnvFile('config.env');
// The sources are processed in the order they are added
// Later sources can override values from earlier sources
factory.load();
const config = factory.toJS();
console.log(config);
You can extend an existing configuration factory with additional properties. This is useful for adding additional configuration options to an existing configuration without modifying the original factory. This feature is particularly helpful when you need to add or override configuration for specific scenarios, such as testing environments.
import * as Fiji from '@ayka/fiji';
const baseFactory = Fiji.init((ctx) => ({
appName: 'BaseApp',
port: ctx.env('PORT', ctx.z.port().default(3000)),
}));
const extendedFactory = baseFactory.extend((ctx) => ({
debug: ctx.env('DEBUG', ctx.z.boolean().default(false)),
}));
extendedFactory.load();
const config = extendedFactory.toJS();
console.log(config);
The patch
method allows you to modify an existing configuration factory by applying changes to specific properties. This is useful for making targeted adjustments to your configuration without recreating the entire factory.
import * as Fiji from '@ayka/fiji';
const baseFactory = Fiji.init((ctx) => ({
appName: 'BaseApp',
port: ctx.env('PORT', ctx.z.port().default(3000)),
}));
const patchedFactory = baseFactory.patch((ctx) => ({
appName: 'PatchedApp',
}));
patchedFactory.load();
const config = patchedFactory.toJS();
console.log(config); // { appName: 'PatchedApp', port: 3000 }
The asClass
method can be particularly useful when integrating with NestJS. By converting your configuration factory into a class, you can easily inject it into your NestJS services or controllers.
import { Injectable } from '@nestjs/common';
import * as Fiji from '@ayka/fiji';
// Initialize the configuration factory
const factory = Fiji.init((ctx) => ({
appName: ctx.value('MyApp'),
port: ctx.env('PORT', ctx.z.port().default(3000)),
debug: ctx.env('DEBUG', ctx.z.boolean().default(false)),
}));
// Load the configuration
factory.load();
// Convert the factory to a class
class Config extends factory.asClass() {}
// Use the class in a NestJS service
@Injectable()
export class AppService {
// Do not forget to add `Config` to module providers
constructor(private readonly config: Config) {}
}
The ConfigFactory
class is the core of the @ayka/fiji
library. It provides methods to initialize, load, and manipulate configuration.
-
init(configSource: initParams): ConfigFactory
- Initializes a new configuration factory with the provided configuration source. The configuration source can be a plain object or a function that receives a context for dynamic configurations.
-
toJS(opts?: toJSOpts): T.config
-
Converts the configuration to a JavaScript object. Optionally hides secrets if the
hideSecrets
option is set totrue
. -
Example:
const factory = Fiji.init((ctx) => ({ apiKey: ctx.value('my-secret-key').secret(), dbPassword: ctx.env('DB_PASSWORD').secret(), })); factory.load(); const config = factory.toJS({ hideSecrets: true }); console.log(config); // { apiKey: '<secret>', dbPassword: '<secret>' }
-
-
load(opts?: loadOpts): ConfigFactory
-
Loads environment variables and applies schemas and defaults. You can specify custom environment sources, files, and additional sources.
-
Example:
const factory = Fiji.init((ctx) => ({ port: ctx.env('PORT', ctx.z.port().default(3000)), dbUrl: ctx.env('DATABASE_URL', ctx.z.string().url()), })); factory.load({ processEnv: { PORT: '8080' }, envFiles: ['.env', 'config.env'], envSources: [{ DATABASE_URL: 'postgres://user:pass@localhost:5432/db' }], }); const config = factory.toJS(); console.log(config); // { port: 8080, dbUrl: 'postgres://user:pass@localhost:5432/db' }
-
-
patch(patch: initParams<T.patch>): ConfigFactory
-
Applies a patch to the existing configuration and returns a new factory instance. This allows you to modify specific properties without recreating the entire configuration.
-
Example:
const factory = Fiji.init((ctx) => ({ apiKey: ctx.value('my-secret-key').secret(), dbPassword: ctx.env('DB_PASSWORD').secret(), })); factory.load(); const newFactory = factory.patch({ apiKey: 'new-secret-key', }); const config = newFactory.toJS(); console.log(config); // { apiKey: 'new-secret-key', dbPassword: '<secret>' }
-
-
extend(extension: initParams): ConfigFactory<T.extension<t, r>>
-
Extends the existing configuration with additional properties and returns a new factory instance. This is useful for adding new configuration options without modifying the original factory.
-
Example:
const factory = Fiji.init((ctx) => ({ apiKey: ctx.value('my-secret-key').secret(), dbPassword: ctx.env('DB_PASSWORD').secret(), })); const extendedFactory = factory.extend({ newProperty: 'new-value', }); extendedFactory.load(); const config = extendedFactory.toJS(); console.log(config); // { apiKey: '<secret>', dbPassword: '<secret>', newProperty: 'new-value' }
-
-
asClass(): configClassConstructor
-
Converts the configuration factory to a class constructor. This is particularly useful for integrating with the NestJS framework. The resulting class can be used as a provider in NestJS modules, allowing for typed configuration injection throughout your application.
-
Example:
import * as Fiji from '@ayka/fiji'; const factory = Fiji.init((ctx) => ({ apiKey: ctx.env('API_KEY').secret(), dbPassword: ctx.env('DB_PASSWORD').secret(), })); factory.load(); class Config extends factory.asClass() {} const config = new Config(); console.log(config.apiKey); console.log(config.dbPassword);
import { Module } from '@nestjs/common'; import { Config } from './config'; @Module({ providers: [Config], }) export class AppModule {}
-
-
addEnvSource(envSource: NodeJS.ProcessEnv): ConfigFactory
- Adds an environment source to the configuration factory. This allows you to programmatically override environment variables.
-
addEnvFile(path = '.env'): ConfigFactory
- Adds an environment source from a file. This is useful for loading environment variables from
.env
files or other configuration files.
- Adds an environment source from a file. This is useful for loading environment variables from
-
parseEnv(envSource: NodeJS.ProcessEnv = process.env): ConfigFactory
- Parses the environment variables and updates the internal environment map. This method ensures that all environment variables are validated and applied according to the defined schemas.
-
envInfo(): { missing: string[], loaded: string[], defaulted: string[], sourced: string[] }
- Provides information about the environment variables, including which are missing, loaded, defaulted, and sourced.
For more detailed examples and API references, please refer to the test files and source code.
Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.
This project makes use of the following libraries:
- Zod: A TypeScript-first schema declaration and validation library.
- ZNV: A library for parsing and validating environment variables.
This project is licensed under the MIT License