@ghii/ghii-v2
v0.0.5
Published
A Funny configuration manager
Readme
GHII - A Funny Configuration Manager
GHII is a powerful, type-safe configuration management library for Node.js applications. It provides a flexible and extensible way to load, validate, and manage configuration data from multiple sources with full TypeScript support.
Table of Contents
- Introduction
- Installation
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Advanced Features
- Best Practices
- Troubleshooting
- Contributing
Introduction
What is GHII?
GHII is a configuration management library that combines the power of validation engines (like Zod) with flexible loading mechanisms. It allows you to:
- Load configuration from multiple sources (files, environment variables, APIs, etc.)
- Validate configuration data with full type safety
- Handle configuration changes with event-driven architecture
- Generate JSON schemas from your configuration definitions
- Maintain configuration snapshots with versioning
Key Features
- 🔒 Type Safety: Full TypeScript support with compile-time type checking
- 🔧 Flexible Loading: Support for multiple configuration sources
- ✅ Validation: Built-in validation with customizable engines
- 📡 Event-Driven: React to configuration changes with events
- 🔄 Snapshot Management: Versioned configuration snapshots
- 📋 Schema Generation: Automatic JSON schema generation
- ⚡ Performance: Efficient deep comparison and caching
Why GHII?
GHII stands out from other configuration managers by providing:
- Type Safety: Unlike many configuration libraries, GHII ensures your configuration is fully typed
- Validation First: Configuration is validated before being used, preventing runtime errors
- Event System: Built-in support for reacting to configuration changes
- Extensible: Easy to add custom validation engines and loaders
- Modern: Built for Node.js 22+ with modern JavaScript features
Installation
Prerequisites
- Node.js 22 or higher
- TypeScript 5.8+ (recommended)
Installation Commands
npm install @ghii/ghii-v2TypeScript Support
GHII is written in TypeScript and provides full type definitions out of the box. No additional @types package is required.
Quick Start
Basic Setup
import { ghii } from '@ghii/ghii-v2';
import { z } from 'zod/v4';
// Define your configuration schema
const configSchema = z.object({
server: z.object({
port: z.number().default(3000),
host: z.string().default('localhost'),
}),
database: z.object({
url: z.string().url(),
poolSize: z.number().min(1).max(20).default(10),
}),
});
// Create a Zod engine
const engine = {
validate: (config: unknown) => {
const result = configSchema.safeParse(config);
if (result.success) {
return { success: true, value: result.data };
} else {
return {
success: false,
errors: result.error.issues.map(issue => ({
path: issue.path.join('.'),
input: issue.input,
details: issue.code,
message: issue.message,
_raw: issue,
})),
};
}
},
toSchema: () => z.toJSONSchema(configSchema),
};
// Initialize GHII
const config = ghii(engine)
.loader(async () => ({
database: {
url: process.env.DATABASE_URL || 'postgresql://localhost:5432/myapp',
},
}))
.loader(async () => ({
server: {
port: parseInt(process.env.PORT || '3000'),
},
}));
// Take a snapshot
const snapshot = await config.takeSnapshot();
console.log(snapshot);Simple Configuration Example
import { ghii } from '@ghii/ghii-v2';
import { z } from 'zod/v4';
// Simple configuration with defaults
const appConfig = ghii({
validate: (config: unknown) => {
const schema = z.object({
name: z.string().default('my-app'),
version: z.string().default('1.0.0'),
debug: z.boolean().default(false),
});
const result = schema.safeParse(config);
return result.success
? { success: true, value: result.data }
: { success: false, errors: result.error.issues.map(/* ... */) };
},
toSchema: () => ({
/* schema */
}),
});
// Load configuration
const config = await appConfig.takeSnapshot();
console.log(config); // { name: 'my-app', version: '1.0.0', debug: false }Core Concepts
Configuration Engine
The configuration engine is responsible for validating configuration data. It must implement two methods:
validate(toValidate): Validates configuration and returns success/error resulttoSchema(): Returns a JSON schema representation of the configuration
Loaders
Loaders are functions that return configuration data. They can be:
- Synchronous or asynchronous
- Load from files, environment variables, APIs, etc.
- Combined to merge multiple configuration sources
Snapshots
A snapshot is a validated, immutable copy of your configuration at a specific point in time. Snapshots are versioned and can be compared for changes.
Validation
All configuration data is validated before being used. Invalid configuration throws detailed error messages with paths to problematic values.
Events
GHII emits events when configuration changes:
ghii:first: Emitted when the first valid configuration is loadedghii:refresh: Emitted when configuration is updated
API Reference
ghii(engine)
Creates a new GHII instance with the specified validation engine.
Parameters:
engine: A validation engine object withvalidateandtoSchemamethods
Returns: A GHII instance with the following methods:
.loader(loaderFunction)
Adds a configuration loader.
Parameters:
loaderFunction: A function that returns configuration data (sync or async)
Returns: The GHII instance for chaining
.takeSnapshot()
Loads and validates configuration from all loaders.
Returns: Promise - The validated configuration
.snapshot()
Returns the current configuration snapshot without reloading.
Throws: Error if no snapshot exists
.waitForSnapshot(options?)
Waits for a valid configuration snapshot.
Parameters:
options.timeout: Timeout in milliseconds (default: 30000)options.onTimeout: Callback when timeout occursoptions.onValidSnapshot: Callback when valid snapshot is available
Returns: Promise
.on(event, listener)
Registers an event listener.
Events:
'ghii:first': Emitted on first valid configuration'ghii:refresh': Emitted when configuration changes
.once(event, listener)
Registers a one-time event listener.
.jsonSchema()
Returns the JSON schema as a string.
Usage Examples
Basic Configuration
import { ghii } from '@ghii/ghii-v2';
import { z } from 'zod';
const config = ghii({
validate: data => {
const schema = z.object({
app: z.object({
name: z.string(),
version: z.string(),
}),
});
const result = schema.safeParse(data);
return result.success
? { success: true, value: result.data }
: { success: false, errors: result.error.issues.map(/* ... */) };
},
toSchema: () => ({
/* schema */
}),
}).loader(() => ({
app: {
name: 'my-app',
version: '1.0.0',
},
}));
const snapshot = await config.takeSnapshot();Environment-based Configuration
const config = ghii(engine).loader(() => ({
database: {
url: process.env.DATABASE_URL,
poolSize: parseInt(process.env.DB_POOL_SIZE || '10'),
},
server: {
port: parseInt(process.env.PORT || '3000'),
host: process.env.HOST || 'localhost',
},
}));File-based Configuration
import { readFileSync } from 'fs';
import { join } from 'path';
const config = ghii(engine).loader(() => {
const configPath = join(process.cwd(), 'config.json');
const configData = JSON.parse(readFileSync(configPath, 'utf8'));
return configData;
});Multiple Loaders
const config = ghii(engine)
// Default configuration
.loader(() => ({
server: { port: 3000, host: 'localhost' },
database: { poolSize: 10 },
}))
// Environment overrides
.loader(() => ({
server: { port: parseInt(process.env.PORT || '3000') },
}))
// File overrides
.loader(async () => {
const fileConfig = await loadConfigFile();
return fileConfig;
});Custom Validation
const config = ghii({
validate: data => {
// Custom validation logic
if (!data.apiKey || data.apiKey.length < 10) {
return {
success: false,
errors: [
{
path: 'apiKey',
input: data.apiKey,
details: 'invalid_length',
message: 'API key must be at least 10 characters',
_raw: null,
},
],
};
}
return { success: true, value: data };
},
toSchema: () => ({
type: 'object',
properties: {
/* ... */
},
}),
});Event Handling
const config = ghii(engine).loader(/* ... */);
// Listen for first configuration
config.once('ghii:first', () => {
console.log('Configuration loaded for the first time');
});
// Listen for configuration changes
config.on('ghii:refresh', activeConfig => {
console.log('Configuration updated:', {
version: activeConfig.version,
config: activeConfig.config,
previousConfig: activeConfig.previousConfig,
});
});
await config.takeSnapshot();Advanced Features
Custom Engines
You can create custom validation engines for different validation libraries:
// Joi engine example
import Joi from 'joi';
function joiEngine(schema: Joi.Schema) {
return {
validate: (data: unknown) => {
const result = schema.validate(data);
if (result.error) {
return {
success: false,
errors: result.error.details.map(detail => ({
path: detail.path.join('.'),
input: detail.context?.value,
details: detail.type,
message: detail.message,
_raw: detail,
})),
};
}
return { success: true, value: result.value };
},
toSchema: () => schema.describe(),
};
}Schema Generation
Generate JSON schemas for documentation or API specifications:
const schema = config.jsonSchema();
console.log(schema);
// Outputs: {"type":"object","properties":{...}}Error Handling
GHII provides detailed error information:
try {
await config.takeSnapshot();
} catch (errors) {
errors.forEach(error => {
console.error(`Error at ${error.path}: ${error.message}`);
console.error(`Input:`, error.input);
console.error(`Details:`, error.details);
});
}Type Safety
GHII provides full TypeScript support:
interface AppConfig {
server: {
port: number;
host: string;
};
database: {
url: string;
poolSize: number;
};
}
const config = ghii<AppConfig>(engine);
// config.snapshot() returns AppConfig
// config.takeSnapshot() returns Promise<AppConfig>Best Practices
Configuration Structure
- Keep configuration flat when possible
- Use descriptive property names
- Group related settings together
- Provide sensible defaults
Loader Design
- Make loaders focused and single-purpose
- Handle errors gracefully in loaders
- Use environment-specific loaders
- Cache expensive operations
Error Handling
- Always validate configuration before use
- Provide meaningful error messages
- Log configuration errors for debugging
- Have fallback configurations
Performance Considerations
- Use efficient validation engines
- Minimize I/O operations in loaders
- Cache configuration when appropriate
- Use deep comparison sparingly
Troubleshooting
Common Issues
"No snapshot found" Error
// ❌ Wrong
const config = config.snapshot();
// ✅ Correct
const config = await config.takeSnapshot();
// or
const config = config.snapshot(); // after takeSnapshot() has been calledValidation Errors
// Check your schema and input data
try {
await config.takeSnapshot();
} catch (errors) {
console.error('Validation errors:', errors);
}Loader Errors
// Ensure loaders return valid data
.loader(async () => {
try {
return await loadConfig();
} catch (error) {
console.error('Loader error:', error);
return {}; // Return empty object as fallback
}
})Debugging Tips
- Enable logging in your loaders
- Check validation errors for detailed information
- Use TypeScript for compile-time error detection
- Test loaders independently before combining them
Error Messages
GHII provides detailed error messages with:
- Path to the problematic value
- Input that caused the error
- Validation details
- Human-readable message
Contributing
Development Setup
- Clone the repository
- Install dependencies:
npm install - Run tests:
npm test - Run linting:
npm run lint
Testing
# Run all tests
npm test
# Run tests with coverage
npm run coverage
# Run tests in watch mode
npm run test:watch
# Run tests with UI
npm run test:uiCode Style
The project uses:
- Biome for linting and formatting
- TypeScript for type safety
- Vitest for testing
Run formatting: npm run format
Pull Request Process
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE file for details.
Maintainers
- Daniele Fiungo - [email protected]
- Nicola Vurchio - [email protected]
- Irene La Bollita - [email protected]
