Package Exports
- configforge
- configforge/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (configforge) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
ConfigForge
A TypeScript library for converting, mapping, and transforming config data in JSON and YAML.
ConfigForge is a universal config converter library with a fluent API
ConfigForge makes it easy to convert configuration files between different formats and structures. Just define your mappings and let ConfigForge handle the rest.
🚀 Quick Start
Installation
npm install configforgeBasic Usage
const { forge } = require('configforge');
// Your source configuration
const sourceConfig = {
name: 'MyApp',
version: '1.0',
author: {
firstName: 'John',
lastName: 'Doe',
},
items: ['apple', 'banana', 'cherry'],
};
// Create converter and define mappings
const converter = forge()
.from('source')
.to('target')
.map('name', 'appName')
.map('version', 'appVersion')
.map('author.firstName', 'authorName')
.map('items[0]', 'firstItem');
// Convert the data
const result = await converter.convert(sourceConfig);
console.log(result.data);
// Output:
// {
// appName: 'MyApp',
// appVersion: '1.0',
// authorName: 'John',
// firstItem: 'apple'
// }String Input Support ⭐ NEW!
ConfigForge now supports converting raw YAML and JSON strings directly:
const { forge } = require('configforge');
// Raw YAML string
const yamlString = `
name: MyApp
version: 2.0
database:
host: localhost
port: 5432
`;
// Raw JSON string
const jsonString = `{
"name": "MyApp",
"version": "2.0",
"database": {
"host": "localhost",
"port": 5432
}
}`;
const converter = forge()
.map('name', 'appName')
.map('version', 'appVersion')
.map('database.host', 'dbHost');
// Method 1: Using convertString() (recommended)
const yamlResult = await converter.convertString(yamlString);
const jsonResult = await converter.convertString(jsonString);
// Method 2: Using convert() with isRawContent option
const result = await converter.convert(yamlString, { isRawContent: true });
console.log(result.data);
// Output:
// {
// appName: 'MyApp',
// appVersion: '2.0',
// dbHost: 'localhost'
// }📖 How It Works
1. Create a Converter
const converter = forge()
.from('sourceSchema') // Define source
.to('targetSchema'); // Define target2. Map Fields
// Simple field mapping
.map('oldField', 'newField')
// Nested object mapping
.map('user.profile.name', 'displayName')
// Array element mapping
.map('items[0]', 'firstItem')
.map('items[1]', 'secondItem')3. Add Transformations
// Transform values during mapping
.map('name', 'displayName', (value) => value.toUpperCase())
// Access source data in transforms
.map('firstName', 'fullName', (firstName, ctx) => {
return `${firstName} ${ctx.source.lastName}`;
})4. Add Conditional Logic
// Function conditions - only apply when condition returns true
.when(source => source.user?.type === 'admin')
.map('user.permissions', 'adminPermissions')
.map('user.role', 'adminRole')
.end()
// String conditions - only apply when field exists and is truthy
.when('user.isActive')
.map('user.lastLogin', 'lastActiveLogin')
.end()5. Merge Multiple Fields
// Combine multiple source fields into one target field
.merge(['firstName', 'lastName'], 'fullName', (first, last) => `${first} ${last}`)
// Merge with transformation
.merge(['basePrice', 'tax'], 'totalPrice',
(price, tax) => price + tax,
total => `$${total.toFixed(2)}`
)
// Merge complex data
.merge(['user.profile', 'user.settings'], 'userInfo', (profile, settings) => ({
...profile,
...settings
}))6. Set Default Values
// Set static default values
.defaults({
version: '1.0.0',
enabled: true,
environment: 'production'
})
// Set dynamic default values using functions
.defaults({
timestamp: () => new Date().toISOString(),
id: () => `user_${Date.now()}`,
sessionId: () => Math.random().toString(36).substr(2, 9)
})7. Add Lifecycle Hooks
// Before hooks - run before conversion starts
.before((sourceData) => {
console.log('Starting conversion...');
// Modify source data if needed
return {
...sourceData,
processed: true
};
})
// After hooks - run after conversion completes
.after((targetData) => {
console.log('Conversion completed!');
// Add computed fields or modify target data
return {
...targetData,
processedAt: new Date().toISOString(),
checksum: calculateChecksum(targetData)
};
})
// Multiple hooks execute in order
.before(validateInput)
.before(preprocessData)
.after(addMetadata)
.after(logResults)
// Async hooks are supported
.before(async (data) => {
const enrichedData = await fetchAdditionalData(data.userId);
return { ...data, ...enrichedData };
})8. Add Validation
const { validators } = require('configforge');
// Add field validation
.validate('email', validators.email)
.validate('age', validators.all(
validators.required,
validators.type('number'),
validators.range(18, 120)
))
.validate('username', validators.all(
validators.required,
validators.minLength(3),
validators.pattern(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores allowed')
))
// Custom validation with context
.validate('password', (value, context) => {
if (context.source.confirmPassword !== value) {
return 'Passwords do not match';
}
return validators.minLength(8)(value, context);
})9. Convert Data
// Convert object data
const result = await converter.convert(sourceConfig);
// Convert from file
const result = await converter.convert('./config.yml');
// Convert raw string content (YAML or JSON) ⭐ NEW!
const yamlString = `
name: MyApp
version: 1.0.0
database:
host: localhost
port: 5432
`;
const result = await converter.convertString(yamlString);
// Alternative: Use convert() with isRawContent option
const result = await converter.convert(yamlString, { isRawContent: true });
// Synchronous conversion (objects only)
const result = converter.convertSync(sourceConfig);🔧 Working with Results
const result = await converter.convert(data);
// Access converted data
console.log(result.data);
// Check conversion statistics
console.log(result.stats);
// {
// fieldsProcessed: 10,
// fieldsMapped: 5,
// fieldsUnmapped: 5,
// transformsApplied: 2,
// duration: 3
// }
// See unmapped fields
console.log(result.unmapped);
// ['author.lastName', 'items[1]', 'items[2]']
// Pretty print report
result.print();
// Save to file
await result.save('./output.json'); // Saves as JSON
await result.save('./output.yml'); // Saves as YAML
// Get as string
const jsonString = result.toJSON();
const yamlString = result.toYAML();📝 Real Examples
Example 1: Simple Config Transformation
const { forge } = require('configforge');
const oldConfig = {
app_name: 'MyApp',
app_version: '2.1.0',
database: {
host: 'localhost',
port: 5432,
},
};
const converter = forge()
.from('old')
.to('new')
.map('app_name', 'name')
.map('app_version', 'version')
.map('database.host', 'db.hostname')
.map('database.port', 'db.port')
.defaults({
environment: 'development',
});
const result = await converter.convert(oldConfig);
console.log(result.data);
// {
// name: 'MyApp',
// version: '2.1.0',
// db: {
// hostname: 'localhost',
// port: 5432
// },
// environment: 'development'
// }Example 2: With Transformations
const userConfig = {
user: {
first_name: 'john',
last_name: 'doe',
email: 'JOHN.DOE@EXAMPLE.COM',
},
settings: {
theme: 'dark',
notifications: 'true',
},
};
const converter = forge()
.from('user')
.to('profile')
.map(
'user.first_name',
'name',
name => name.charAt(0).toUpperCase() + name.slice(1)
)
.map('user.email', 'email', email => email.toLowerCase())
.map('user.first_name', 'displayName', (firstName, ctx) => {
const lastName = ctx.source.user.last_name;
return `${firstName} ${lastName}`.replace(/\b\w/g, l => l.toUpperCase());
})
.map(
'settings.notifications',
'notificationsEnabled',
value => value === 'true'
);
const result = await converter.convert(userConfig);
console.log(result.data);
// {
// name: 'John',
// email: 'john.doe@example.com',
// displayName: 'John Doe',
// notificationsEnabled: true
// }Example 3: Array Processing with forEach() ⭐ ENHANCED!
// Simple fruit inventory
const fruitData = {
storeId: 'STORE-001',
fruits: [
{
name: 'Apple',
color: 'red',
price: 1.5,
quantity: 100,
},
{
name: 'Banana',
color: 'yellow',
price: 0.75,
quantity: 80,
},
{
name: 'Orange',
color: 'orange',
price: 1.25,
quantity: 60,
},
],
};
// Method 1: Basic forEach (maps to same field name)
const converter1 = forge()
.from('inventory')
.to('catalog')
.map('storeId', 'storeId')
.forEach('fruits', (fruit, index) => {
// index is always a number for arrays ⭐ NEW!
return {
id: index + 1, // Now works correctly!
fruitName: fruit.name,
displayColor: fruit.color,
pricePerItem: fruit.price,
inStock: fruit.quantity,
totalValue: fruit.price * fruit.quantity,
isExpensive: fruit.price > 1.0,
};
});
// Method 2: forEach with target renaming ⭐ NEW!
const converter2 = forge()
.from('inventory')
.to('catalog')
.map('storeId', 'storeId')
.forEach('fruits', 'products', (fruit, index) => {
// Rename 'fruits' to 'products' in output
return {
id: index + 1,
name: fruit.name,
price: fruit.price,
available: fruit.quantity > 0,
category: fruit.price > 1.0 ? 'premium' : 'budget',
};
});
const result1 = await converter1.convert(fruitData);
console.log('Basic forEach result:', result1.data);
// {
// storeId: 'STORE-001',
// fruits: [
// {
// id: 1,
// fruitName: 'Apple',
// displayColor: 'red',
// pricePerItem: 1.50,
// inStock: 100,
// totalValue: 150,
// isExpensive: true
// },
// // ... more fruits
// ]
// }
const result2 = await converter2.convert(fruitData);
console.log('Renamed forEach result:', result2.data);
// {
// storeId: 'STORE-001',
// products: [ // ← Renamed from 'fruits' to 'products'
// {
// id: 1,
// name: 'Apple',
// price: 1.50,
// available: true,
// category: 'premium'
// },
// // ... more products
// ]
// }Example 4: Array to Object Transformation with Enhanced forEach() ⭐ NEW!
// NPC data from Citizens plugin (array format)
const citizensData = {
sourcePlugin: 'Citizens',
npc: [
{
uuid: '9e1d174d-bc6c-495e-bd70-17e60327b716',
name: 'Guard Captain',
level: 25,
location: { x: 100, y: 64, z: 200 },
},
{
uuid: '1731110a-3586-48cd-859c-a9d3ea2c7797',
name: 'Village Merchant',
level: 15,
location: { x: 150, y: 64, z: 180 },
},
{
uuid: '63ac9d5b-9d5e-461a-9bf7-656361692f8',
name: 'Wise Wizard',
level: 50,
location: { x: 80, y: 70, z: 220 },
},
],
};
// Convert array to object with UUID keys using enhanced forEach
const converter = forge()
.from('Citizens')
.to('FancyNPCs')
.map('sourcePlugin', 'plugin')
.forEach(
'npc',
'npcs',
(npc, index) => {
// Return object where UUID becomes the key
return {
[npc.uuid]: {
id: index + 1,
displayName: npc.name,
level: npc.level,
position: {
world: 'world',
x: npc.location.x,
y: npc.location.y,
z: npc.location.z,
},
settings: {
invulnerable: npc.level > 30,
glowing: npc.level >= 50,
},
},
};
},
{ output: 'object' }
); // ⭐ NEW: Enhanced forEach with object output
const result = await converter.convert(citizensData);
console.log(result.data);
// {
// plugin: 'Citizens',
// npcs: {
// '9e1d174d-bc6c-495e-bd70-17e60327b716': {
// id: 1,
// displayName: 'Guard Captain',
// level: 25,
// position: { world: 'world', x: 100, y: 64, z: 200 },
// settings: { invulnerable: false, glowing: false }
// },
// '1731110a-3586-48cd-859c-a9d3ea2c7797': {
// id: 2,
// displayName: 'Village Merchant',
// level: 15,
// position: { world: 'world', x: 150, y: 64, z: 180 },
// settings: { invulnerable: false, glowing: false }
// },
// '63ac9d5b-9d5e-461a-9bf7-656361692f8': {
// id: 3,
// displayName: 'Wise Wizard',
// level: 50,
// position: { world: 'world', x: 80, y: 70, z: 220 },
// settings: { invulnerable: true, glowing: true }
// }
// }
// }
// Key differences between forEach output options:
//
// forEach(..., { output: 'array' }) - Default behavior:
// npcs: [
// { id: 1, name: 'Guard' }, // npcs[0]
// { id: 2, name: 'Merchant' } // npcs[1]
// ]
//
// forEach(..., { output: 'object' }) - Object transformation:
// npcs: {
// 'uuid-1': { id: 1, name: 'Guard' }, // npcs['uuid-1']
// 'uuid-2': { id: 2, name: 'Merchant' } // npcs['uuid-2']
// }
// Advanced usage with filtering
const activeUsersConverter = forge().forEach(
'users',
'activeUsers',
(user, index) => {
// Filter out inactive users by returning null
if (!user.active) return null;
return {
[user.id]: {
name: user.name,
lastLogin: user.lastLogin,
index: index + 1,
},
};
},
{ output: 'object' }
);
// Works with objects too - transforms object keys
const servicesConverter = forge().forEach(
'config',
'services',
(service, key) => {
return {
[`${key}_service`]: {
type: key,
endpoint: `${service.host}:${service.port}`,
config: service,
},
};
},
{ output: 'object' }
);Example 5: Conditional Mapping with when()
// Different types of pets need different mappings
const petData = {
type: 'dog',
name: 'Buddy',
age: 3,
dogInfo: {
breed: 'Golden Retriever',
tricks: ['sit', 'stay', 'fetch'],
},
catInfo: {
breed: 'Persian',
indoor: true,
},
birdInfo: {
species: 'Parrot',
canTalk: true,
},
};
const converter = forge()
.from('petData')
.to('petProfile')
.map('name', 'petName')
.map('age', 'petAge')
.map('type', 'animalType')
// Only map dog-specific info for dogs
.when(source => source.type === 'dog')
.map('dogInfo.breed', 'breed')
.map('dogInfo.tricks', 'knownTricks')
.end()
// Only map cat-specific info for cats
.when(source => source.type === 'cat')
.map('catInfo.breed', 'breed')
.map('catInfo.indoor', 'isIndoorCat')
.end()
// Only map bird-specific info for birds
.when(source => source.type === 'bird')
.map('birdInfo.species', 'species')
.map('birdInfo.canTalk', 'canSpeak')
.end();
const result = await converter.convert(petData);
console.log(result.data);
// {
// petName: 'Buddy',
// petAge: 3,
// animalType: 'dog',
// breed: 'Golden Retriever',
// knownTricks: ['sit', 'stay', 'fetch']
// }Example 5: Merge Multiple Fields with merge()
// Simple student report card
const studentData = {
student: {
firstName: 'Alice',
lastName: 'Smith',
},
grades: {
math: 85,
english: 92,
science: 78,
},
activities: ['soccer', 'chess', 'art'],
info: {
grade: '5th',
teacher: 'Ms. Johnson',
},
};
const converter = forge()
.from('report')
.to('summary')
// Merge student name fields
.merge(
['student.firstName', 'student.lastName'],
'studentName',
(first, last) => `${first} ${last}`
)
// Calculate total with transformation
.merge(
['order.basePrice', 'order.tax', 'order.shipping'],
'subtotal',
(base, tax, shipping) => base + tax + shipping
)
.merge(
['order.basePrice', 'order.tax', 'order.shipping', 'order.discount'],
'total',
(base, tax, shipping, discount) => base + tax + shipping - discount,
total => `$${total.toFixed(2)}`
) // Transform to currency format
// Merge arrays and objects
.merge(['items', 'metadata'], 'orderSummary', (items, meta) => ({
itemCount: items.length,
items: items.join(', '),
...meta,
}))
// Simple field mappings
.map('customer.email', 'billingEmail');
const result = await converter.convert(orderData);
console.log(result.data);
// {
// customerName: 'John Doe',
// subtotal: 121.49,
// total: '$106.49',
// orderSummary: {
// itemCount: 3,
// items: 'laptop, mouse, keyboard',
// orderDate: '2023-12-01',
// source: 'web'
// },
// billingEmail: 'john.doe@example.com'
// }Example 6: Data Validation
const { forge, validators } = require('configforge');
// User registration form data
const formData = {
personalInfo: {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
age: 28,
},
accountInfo: {
username: 'johndoe123',
password: 'SecurePass123!',
confirmPassword: 'SecurePass123!',
},
preferences: {
newsletter: 'yes',
theme: 'dark',
language: 'en',
},
};
const converter = forge()
.from('form')
.to('user')
// Map fields
.merge(
['personalInfo.firstName', 'personalInfo.lastName'],
'fullName',
(first, last) => `${first} ${last}`
)
.map('personalInfo.email', 'email')
.map('personalInfo.age', 'age')
.map('accountInfo.username', 'username')
.map('accountInfo.password', 'password')
.map(
'preferences.newsletter',
'subscribeToNewsletter',
value => value === 'yes'
)
.map('preferences.theme', 'theme')
.map('preferences.language', 'language')
// Add validation rules
.validate(
'fullName',
validators.all(
validators.required,
validators.minLength(2),
validators.maxLength(100)
)
)
.validate('email', validators.all(validators.required, validators.email))
.validate(
'age',
validators.all(
validators.required,
validators.type('number'),
validators.range(13, 120)
)
)
.validate(
'username',
validators.all(
validators.required,
validators.minLength(3),
validators.maxLength(20),
validators.pattern(
/^[a-zA-Z0-9_]+$/,
'Username can only contain letters, numbers, and underscores'
)
)
)
.validate(
'password',
validators.all(
validators.required,
validators.minLength(8),
validators.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
'Password must contain lowercase, uppercase, and number'
)
)
)
.validate('theme', validators.oneOf(['light', 'dark', 'auto']))
.validate('language', validators.oneOf(['en', 'es', 'fr', 'de']))
// Custom validation
.validate('password', (value, context) => {
if (context.source.accountInfo.confirmPassword !== value) {
return 'Passwords do not match';
}
return true;
});
const result = await converter.convert(formData);
if (result.errors.length > 0) {
console.log('Validation errors:');
result.errors.forEach(error => {
console.log(`- ${error.path}: ${error.message}`);
});
} else {
console.log('User data is valid!');
console.log(result.data);
// {
// fullName: 'John Doe',
// email: 'john.doe@example.com',
// age: 28,
// username: 'johndoe123',
// password: 'SecurePass123!',
// subscribeToNewsletter: true,
// theme: 'dark',
// language: 'en'
// }
}Example 7: Defaults and Hooks
// Student data with missing fields
const studentData = {
student: {
firstName: 'Alice',
lastName: 'Smith',
// age is missing
},
grades: {
math: 85,
english: 92,
// science grade is missing
},
// activities array is missing
info: {
grade: '5th',
teacher: 'Ms. Johnson',
// school year is missing
},
};
const converter = forge()
.from('studentReport')
.to('completeProfile')
// Basic mappings
.map('student.firstName', 'firstName')
.map('student.lastName', 'lastName')
.map('student.age', 'age')
.map('grades.math', 'mathGrade')
.map('grades.english', 'englishGrade')
.map('grades.science', 'scienceGrade')
.map('activities', 'extracurriculars')
.map('info.grade', 'gradeLevel')
.map('info.teacher', 'teacher')
.map('info.schoolYear', 'academicYear')
// Set default values for missing fields
.defaults({
age: 10, // Default age for 5th graders
scienceGrade: 80, // Default science grade
extracurriculars: ['reading'], // Default activity
academicYear: () => {
// Dynamic default - current school year
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
return month >= 8 ? `${year}-${year + 1}` : `${year - 1}-${year}`;
},
status: 'active',
lastUpdated: () => new Date().toISOString(),
})
// Add before hook to log conversion start
.before(data => {
console.log('🔄 Starting conversion...');
console.log(
`Processing student: ${data.student?.firstName} ${data.student?.lastName}`
);
return data; // Return the data unchanged
})
// Add after hook to calculate grade average
.after(data => {
console.log('✅ Conversion completed!');
// Create full name from first and last name
if (data.firstName && data.lastName) {
data.fullName = `${data.firstName} ${data.lastName}`;
}
// Calculate and add grade average
const { mathGrade, englishGrade, scienceGrade } = data;
if (mathGrade && englishGrade && scienceGrade) {
data.gradeAverage = Math.round(
(mathGrade + englishGrade + scienceGrade) / 3
);
console.log(`Calculated grade average: ${data.gradeAverage}`);
}
return data; // Return the modified data
});
const result = await converter.convert(studentData);
console.log(result.data);
// {
// firstName: 'Alice',
// lastName: 'Smith',
// mathGrade: 85,
// englishGrade: 92,
// gradeLevel: '5th',
// teacher: 'Ms. Johnson',
// age: 10,
// scienceGrade: 80,
// extracurriculars: ['reading'],
// academicYear: '2024-2025',
// status: 'active',
// lastUpdated: '2024-12-23T06:22:05.491Z',
// fullName: 'Alice Smith',
// gradeAverage: 86
// }Example 8: String Input Support
const { forge } = require('configforge');
// Example: Converting raw YAML string content
const yamlConfigString = `
# Server Configuration
server:
host: localhost
port: 8080
ssl: true
# Database Configuration
database:
type: postgresql
host: db.example.com
port: 5432
name: myapp
credentials:
username: admin
password: secret123
# Features
features:
- authentication
- logging
- monitoring
`;
// Example: Converting raw JSON string content
const jsonConfigString = `{
"api": {
"version": "v2",
"baseUrl": "https://api.example.com",
"timeout": 30000
},
"cache": {
"enabled": true,
"ttl": 3600,
"provider": "redis"
},
"logging": {
"level": "info",
"format": "json"
}
}`;
// Create converter for YAML string
const yamlConverter = forge()
.from('legacy-config')
.to('standard-config')
.map('server.host', 'app.host')
.map('server.port', 'app.port')
.map('server.ssl', 'app.secure')
.map('database.host', 'db.host')
.map('database.port', 'db.port')
.map('database.name', 'db.database')
.map('database.credentials.username', 'db.user')
.map('database.credentials.password', 'db.password')
.map('features', 'app.enabledFeatures')
.defaults({
'app.environment': 'production',
'app.debug': false,
});
// Method 1: Using convertString() - recommended for string content
const yamlResult = await yamlConverter.convertString(yamlConfigString);
console.log('YAML Result:', yamlResult.data);
// {
// app: {
// host: 'localhost',
// port: 8080,
// secure: true,
// enabledFeatures: ['authentication', 'logging', 'monitoring'],
// environment: 'production',
// debug: false
// },
// db: {
// host: 'db.example.com',
// port: 5432,
// database: 'myapp',
// user: 'admin',
// password: 'secret123'
// }
// }
// Method 2: Using convert() with isRawContent option
const alternativeResult = await yamlConverter.convert(yamlConfigString, {
isRawContent: true,
});
console.log('Alternative Result:', alternativeResult.data);
// Create converter for JSON string
const jsonConverter = forge()
.from('api-config')
.to('service-config')
.map('api.version', 'service.apiVersion')
.map('api.baseUrl', 'service.endpoint')
.map('api.timeout', 'service.requestTimeout')
.map('cache.enabled', 'service.caching.enabled')
.map('cache.ttl', 'service.caching.duration')
.map('logging.level', 'service.log.level')
.defaults({
'service.retries': 3,
'service.caching.provider': 'memory',
});
// Convert JSON string content
const jsonResult = await jsonConverter.convertString(jsonConfigString);
console.log('JSON Result:', jsonResult.data);
// {
// service: {
// apiVersion: 'v2',
// endpoint: 'https://api.example.com',
// requestTimeout: 30000,
// caching: {
// enabled: true,
// duration: 3600,
// provider: 'memory'
// },
// log: {
// level: 'info'
// },
// retries: 3
// }
// }
// Format auto-detection works for both YAML and JSON
const mixedConverter = forge()
.map('name', 'appName')
.map('version', 'appVersion');
// Auto-detects YAML format
const yamlData = await mixedConverter.convertString(`
name: MyApp
version: 1.0.0
`);
// Auto-detects JSON format
const jsonData = await mixedConverter.convertString(`{
"name": "MyApp",
"version": "1.0.0"
}`);
// Error handling for malformed content
try {
const malformedResult = await converter.convertString(`{
"name": "Test"
"missing": "comma"
}`);
} catch (error) {
console.log('Parse error handled gracefully:', error.message);
}Example 9: File Conversion
// Convert YAML file to JSON structure
const converter = forge()
.from('yaml')
.to('json')
.map('server.host', 'hostname')
.map('server.port', 'port')
.map('database.url', 'dbUrl');
// Read and convert file
const result = await converter.convert('./config.yml');
// Save as JSON
await result.save('./config.json');Example 9: Plugin System with Built-in Plugins
const {
forge,
createValidationPlugin,
createAuditPlugin,
createBackupPlugin,
} = require('configforge');
// Create converter with plugin support
const converter = forge()
.from('userForm')
.to('userProfile')
.map('personalInfo.firstName', 'firstName')
.map('personalInfo.lastName', 'lastName')
.map('personalInfo.email', 'email')
.map('accountInfo.username', 'username')
.merge(
['personalInfo.firstName', 'personalInfo.lastName'],
'fullName',
(first, last) => `${first} ${last}`
);
// Add built-in plugins
const validationPlugin = createValidationPlugin({
strictMode: true,
failOnWarnings: false,
});
const auditPlugin = createAuditPlugin({
logFile: './conversion-audit.log',
logLevel: 'detailed',
includeData: false,
});
const backupPlugin = createBackupPlugin({
backupDir: './backups',
keepVersions: 5,
timestampFormat: 'YYYY-MM-DD_HH-mm-ss',
});
// Install plugins
await converter.use(validationPlugin);
await converter.use(auditPlugin);
await converter.use(backupPlugin);
// Convert with plugin enhancements
const userData = {
personalInfo: {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
},
accountInfo: {
username: 'johndoe123',
},
};
const result = await converter.convert(userData);
// Plugins automatically:
// - Validate data quality (ValidationPlugin)
// - Log conversion activities (AuditPlugin)
// - Create backups of source data (BackupPlugin)
// - Handle errors gracefully with detailed reporting
console.log(result.data);
// {
// firstName: 'John',
// lastName: 'Doe',
// email: 'john.doe@example.com',
// username: 'johndoe123',
// fullName: 'John Doe'
// }
// Check plugin results
console.log('Validation results:', result.context?.validationResults);
console.log('Backup created:', result.context?.backupPath);Example 10: Batch Processing and Performance Features
const {
forge,
BatchProcessor,
IncrementalProcessor,
PerformanceMonitor,
} = require('configforge');
// Create converter for processing multiple config files
const converter = forge()
.from('legacy-config')
.to('modern-config')
.map('app_name', 'application.name')
.map('app_version', 'application.version')
.map('database.host', 'db.hostname')
.map('database.port', 'db.port')
.map('server.port', 'application.port')
.defaults({
environment: 'production',
createdAt: () => new Date().toISOString(),
});
// 1. Batch Processing - Convert multiple files efficiently
const batchResult = await converter.convertBatch(
['./configs/app1.yml', './configs/app2.yml', './configs/app3.yml'],
{
parallel: true,
workers: 3,
errorStrategy: 'continue',
progressCallback: progress => {
console.log(`Progress: ${progress.completed}/${progress.total} files`);
},
}
);
console.log(
`Processed ${batchResult.stats.successfulFiles} files successfully`
);
console.log(`Failed: ${batchResult.stats.failedFiles} files`);
console.log(`Total duration: ${batchResult.stats.totalDuration}ms`);
// 2. Incremental Processing - Only process changed files
const incrementalResult = await converter.convertIncremental(
'./config.yml',
'./output/config.json',
'./.cache' // Cache directory
);
if (incrementalResult) {
console.log('File was processed (changed since last run)');
} else {
console.log('File skipped (no changes detected)');
}
// 3. Incremental Batch Processing
const incrementalBatchResult = await converter.convertIncrementalBatch(
['./configs/app1.yml', './configs/app2.yml', './configs/app3.yml'],
'./output',
{
cacheDir: './.cache',
getOutputPath: inputPath => {
const fileName = inputPath.split('/').pop().replace('.yml', '.json');
return `./output/${fileName}`;
},
}
);
console.log(`Processed: ${incrementalBatchResult.processed.length} files`);
console.log(
`Skipped: ${incrementalBatchResult.skipped.length} files (unchanged)`
);
// 4. Performance Monitoring
const monitor = new PerformanceMonitor();
monitor.start();
// Track individual steps
monitor.startStep('parse');
const result = await converter.convert('./large-config.yml');
monitor.endStep('parse');
// Record file sizes for throughput calculation
monitor.recordSizes(1024 * 1024, 800 * 1024); // 1MB input, 800KB output
// Get detailed performance metrics
const metrics = monitor.finish(result.stats);
const report = monitor.createReport(metrics);
console.log(report);
// === Performance Report ===
//
// Timing Breakdown:
// Parse: 15.23ms
// Map: 8.45ms
// Transform: 3.21ms
// Validate: 2.10ms
// Serialize: 5.67ms
// Total: 34.66ms
//
// Throughput:
// Fields/sec: 2890
// Transforms/sec: 1445
// Bytes/sec: 29.64 MB/s
//
// Memory Usage:
// Heap Used: 45.23 MB
// Heap Total: 67.89 MB
// RSS: 89.12 MB
//
// File Sizes:
// Input: 1.00 MB
// Output: 800.00 KB
// Ratio: 80.0%
// 5. Performance Benchmarking
const { PerformanceBenchmark } = require('configforge');
const sequentialOperation = async () => {
return converter.convertBatch(['./config1.yml', './config2.yml'], {
parallel: false,
});
};
const parallelOperation = async () => {
return converter.convertBatch(['./config1.yml', './config2.yml'], {
parallel: true,
workers: 2,
});
};
const benchmarks = [
{ name: 'sequential-processing', operation: sequentialOperation },
{ name: 'parallel-processing', operation: parallelOperation },
];
const benchmarkResults = await PerformanceBenchmark.compareBenchmarks(
benchmarks,
10
);
const comparisonReport =
PerformanceBenchmark.createComparisonReport(benchmarkResults);
console.log(comparisonReport);
// === Benchmark Comparison ===
//
// Results (sorted by average time):
// parallel-processing:
// Average: 45.23ms (fastest)
// Range: 42.10ms - 48.90ms
// Std Dev: 2.15ms
// Ops/sec: 22
//
// sequential-processing:
// Average: 78.45ms (1.73x slower)
// Range: 75.20ms - 82.10ms
// Std Dev: 2.89ms
// Ops/sec: 13
// 6. Advanced Batch Processing with Custom Options
const advancedBatchResult = await converter.convertBatch(
[
'./configs/*.yml', // Glob patterns supported
],
{
parallel: true,
workers: 4,
errorStrategy: 'fail-fast', // Stop on first error
progressCallback: progress => {
const percentage = Math.round(
(progress.completed / progress.total) * 100
);
console.log(`[${percentage}%] Processing: ${progress.current}`);
if (progress.failed > 0) {
console.log(`⚠️ ${progress.failed} files failed`);
}
},
}
);
// Save all results to output directory
const batchProcessor = new BatchProcessor(converter);
await batchProcessor.saveBatch(advancedBatchResult.results, './output', [
'./configs/app1.yml',
'./configs/app2.yml',
]);
// 7. Cache Management for Incremental Processing
const incrementalProcessor = new IncrementalProcessor(converter, './.cache');
// Get cache statistics
const cacheStats = incrementalProcessor.getCacheStats();
console.log(`Cache entries: ${cacheStats.totalEntries}`);
console.log(`Successful: ${cacheStats.successfulEntries}`);
console.log(`Failed: ${cacheStats.failedEntries}`);
// Clean up stale cache entries
const removedEntries = await incrementalProcessor.cleanupCache();
console.log(`Removed ${removedEntries} stale cache entries`);
// Clear entire cache
await incrementalProcessor.clearCache();
console.log('Cache cleared');Example 11: CLI Generation System
const { forge, CLIGenerator } = require('configforge');
// Create your converter
const converter = forge()
.from('legacy')
.to('modern')
.map('app_name', 'name')
.map('app_version', 'version')
.map('database.host', 'db.hostname')
.map('database.port', 'db.port')
.defaults({
environment: 'production',
});
// Generate CLI for your converter
const cli = CLIGenerator.forConverter(converter, {
name: 'config-converter',
version: '1.0.0',
description: 'Convert legacy config to modern format',
});
// Add custom commands
cli.addCommand({
name: 'migrate',
description: 'Migrate all configs in a directory',
options: [
{
flags: '-d, --directory <dir>',
description: 'Directory containing config files',
},
],
action: async options => {
// Custom migration logic
console.log(`Migrating configs in ${options.directory}`);
},
});
// Parse command line arguments
cli.parse();
// Now you can use your CLI:
// $ config-converter convert input.yml output.json
// $ config-converter validate config.yml
// $ config-converter profile save my-config
// $ config-converter profile list
// $ config-converter migrate -d ./configs🎯 Key Features
- ✅ Simple API: Just map fields and convert
- ✅ String Input Support: Convert raw YAML/JSON strings directly with
convertString()⭐ NEW! - ✅ Pipeline Architecture: Robust internal processing with configurable steps and error handling
- ✅ No direct Mapper usage needed: The
convert()method handles everything - ✅ Nested object support: Use dot notation like
user.profile.name - ✅ Array access: Use bracket notation like
items[0] - ✅ Transformations: Transform values during mapping
- ✅ Conditional mapping: Use
when()for conditional logic - ✅ Array/object processing: Use
forEach()with flexible output options for iteration and transformation ⭐ ENHANCED! - ✅ Smart Field Tracking: Automatically tracks fields accessed in
forEachoperations for accurate mapping statistics ⭐ NEW! - ✅ Multi-field merging: Use
merge()to combine multiple sources into one target - ✅ Default values: Set fallback values
- ✅ Comprehensive Error Handling: Advanced error reporting with context and suggestions ⭐ ENHANCED!
- ✅ CLI Generation System: Create command-line interfaces for converters ⭐ ENHANCED!
- ✅ Plugin System: Extensible architecture with built-in plugins for validation, auditing, and backups ⭐ ENHANCED!
- ✅ Batch Processing: Efficiently process multiple files with parallel support and progress tracking ⭐ ENHANCED!
- ✅ Incremental Processing: Only process changed files using content hashing and modification times ⭐ ENHANCED!
- ✅ Performance Monitoring: Detailed performance tracking, benchmarking, and reporting ⭐ ENHANCED!
- ✅ File support: Convert YAML, JSON files directly
- ✅ Statistics: Get detailed conversion reports
- ✅ TypeScript: Full type safety
🔄 Current Implementation Status
✅ Working Features:
- Basic field mapping with
map() - Nested object and array access
- Value transformations
- String input support with
convertString()andconvert()withisRawContentoption ⭐ NEW! - Default values with
defaults()⭐ ENHANCED! - Array/object processing with
forEach()andmapToObject()⭐ ENHANCED! - Conditional mapping with
when() - Multi-field merging with
merge() - Field validation with
validate() - Lifecycle hooks with
before()andafter()⭐ NEW! - Advanced error handling and reporting system ⭐ NEW!
- CLI generation system with command-line interfaces ⭐ ENHANCED!
- Plugin system foundation with built-in plugins ⭐ NEW!
- Batch processing with parallel support and progress tracking ⭐ ENHANCED!
- Incremental processing with content hashing and modification times ⭐ ENHANCED!
- Performance monitoring with detailed benchmarking and reporting ⭐ ENHANCED!
- File parsing (YAML, JSON)
- Conversion statistics and reporting
- Async and sync conversion support
🚧 Coming Soon:
- Advanced plugin ecosystem
💡 Tips
- You don't need to use the Mapper class directly - just use
converter.convert() - Use dot notation for nested objects:
'user.profile.name' - Use bracket notation for arrays:
'items[0]','items[1]' - Transform functions get the value and context:
(value, ctx) => { ... } - Use conditional mapping with
when()for type-specific logic:when(source => source.accountType === 'premium') - Use merge() to combine multiple fields:
merge(['field1', 'field2'], 'result', (a, b) => a + b) - Use validation to ensure data quality:
validate('email', validators.email) - Combine validators with
validators.all()for multiple rules - Always call
.end()after conditional mappings to return to the main converter - Check
result.errorsto see validation failures - Check
result.unmappedto see which fields weren't mapped - Use
result.print()for a nice conversion report - Use
defaults()to provide fallback values for missing fields:defaults({ status: 'active' }) - Use function defaults for dynamic values:
defaults({ timestamp: () => new Date().toISOString() }) - Use
before()hooks to preprocess source data before conversion - Use
after()hooks to postprocess target data after conversion - Hooks can be async - just use
async (data) => { ... }and they'll be awaited - Multiple hooks execute in order - add as many as you need for complex workflows
- Error handling provides helpful suggestions - when field mapping fails, you'll get suggestions for similar field names
- Errors include rich context - see exactly where and why conversions failed with detailed error information
- Use error categories to filter and handle different types of errors (validation, mapping, parsing, etc.)
- Create CLIs for converters - use
CLIGenerator.forConverter(converter)to generate command-line interfaces - Use CLI profiles - save converter configurations as profiles for reuse:
cli profile save my-converter - CLI supports batch processing - convert multiple files at once with pattern matching and parallel processing
- Use built-in plugins - leverage ValidationPlugin, AuditPlugin, and BackupPlugin for enhanced functionality
- Plugin hooks execute in order - plugins can intercept and modify data at different conversion stages
- Error hooks provide recovery - plugins can handle errors gracefully and provide fallback behavior
- Use batch processing for multiple files -
convertBatch()processes multiple files efficiently with parallel support - Enable parallel processing - set
parallel: trueand configureworkersfor faster batch processing - Use incremental processing -
convertIncremental()only processes files that have changed since last run - Monitor performance - use
PerformanceMonitorto track conversion speed, memory usage, and throughput - Benchmark different approaches - use
PerformanceBenchmarkto compare sequential vs parallel processing - Configure error strategies - use
errorStrategy: 'continue'to process all files even if some fail, or'fail-fast'to stop on first error - Use
forEach()with{ output: 'object' }to transform arrays into keyed objects - perfect for converting indexed arrays to UUID-keyed objects forEach()with object output merges returned objects - return{ [key]: value }to create dynamic keys from your data- Filter with
forEach()- returnnullfrom the mapping function to exclude items from the result - Choose forEach output type - use
{ output: 'array' }(default) to maintain structure,{ output: 'object' }to create keyed objects - Track progress - provide
progressCallbackto monitor batch processing progress in real-time - Cache management - use
IncrementalProcessor.getCacheStats()andcleanupCache()to manage incremental processing cache - Save batch results - use
BatchProcessor.saveBatch()to save all conversion results to an output directory - Use forge() with options - pass
ForgeOptionstoforge({ strict: true, parallel: true })to configure converter behavior globally - Use convertString() for raw content - when you have YAML or JSON as a string, use
convertString()instead ofconvert()⭐ NEW! - Use convert() with isRawContent option - alternatively, use
convert(stringContent, { isRawContent: true })for string input ⭐ NEW! - Format auto-detection works with strings - ConfigForge automatically detects YAML vs JSON format in string content ⭐ NEW!
- String input handles parsing errors gracefully - malformed YAML/JSON strings will produce helpful error messages ⭐ NEW!
- forEach field tracking is automatic - when you access fields inside forEach callbacks, they're automatically marked as mapped in statistics ⭐ NEW!
- forEach with target renaming - use
forEach('source', 'target', mapFn)to rename arrays/objects in output:forEach('npc', 'npcs', mapFn)⭐ NEW! - Array indices are always numbers - in forEach for arrays, the index parameter is guaranteed to be a number, so
index + 1works correctly ⭐ NEW! - Object keys in forEach - for objects, forEach passes the key as the second parameter for backward compatibility ⭐ NEW!
- Access object keys in context - when processing objects with forEach, the key is also available in
context.metadata.objectKey⭐ NEW!
That's it! ConfigForge makes config conversion simple and straightforward. No complex setup, no direct class manipulation - just define your mappings and convert.
License
This package is licensed under the PolyForm Noncommercial License 1.0.0.
See the LICENSE file for full terms.