npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

configforge

v1.8.0

Published

Universal config converter framework with exceptional developer experience

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 configforge

Basic 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 target

2. 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]']

// Handle errors
if (result.errors.length > 0) {
  // Technical errors (with stack traces)
  console.log(result.errors);

  // User-friendly errors (clean messages) ⭐ NEW!
  console.log(result.getUserErrors());
  // ['Failed to parse config.yml: Map keys must be unique at line 22']
}

// 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: '[email protected]',
  },
  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: '[email protected]',
//   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: Object Flattening with forEach() ⭐ NEW!

// Problem: You have nested configuration with wrapper objects that you want to remove
const announcementsConfig = {
  plugin: 'AutoAnnounce',
  version: '2.1.0',
  Announcements: {
    store: {
      interval: 61,
      sound: 'ENTITY_EXPERIENCE_ORB_PICKUP',
      lines: [
        '<center>&6Visit our store!</center>',
        '<center>&eGet 20% off today!</center>',
      ],
    },
    discord: {
      interval: 120,
      sound: 'ENTITY_PLAYER_LEVELUP',
      lines: [
        '<center>&9Join our Discord!</center>',
        '<center>&b/discord for the link</center>',
      ],
    },
    events: {
      interval: 300,
      sound: 'ENTITY_FIREWORK_ROCKET_LAUNCH',
      lines: [
        '<center>&dUpcoming Events!</center>',
        '<center>&fCheck /events</center>',
      ],
    },
  },
};

// Goal: Remove the "Announcements" wrapper and promote child objects to root level
const converter = forge()
  .from('announcements')
  .to('schedules')
  .map('plugin', 'sourcePlugin')
  .map('version', 'configVersion')
  .forEach(
    'Announcements',
    (announcement, key) => {
      return {
        [key]: {
          Enabled: true,
          MinPlayers: 1,
          FeedBack: false,
          Repeat: true,
          Delay: announcement.interval,
          Commands: announcement.lines.map(line => {
            // Clean up HTML tags and convert to broadcast commands
            const cleanLine = line.replace(/<\/?center>/g, '');
            return `broadcast! ${cleanLine}`;
          }),
        },
      };
    },
    { flatten: true, output: 'object' } // ⭐ NEW: Flatten removes the wrapper!
  );

const result = await converter.convert(announcementsConfig);
console.log(result.data);

// Without flatten (old behavior):
// {
//   sourcePlugin: 'AutoAnnounce',
//   configVersion: '2.1.0',
//   Announcements: {           ← Wrapper object remains
//     store: { Enabled: true, MinPlayers: 1, ... },
//     discord: { Enabled: true, MinPlayers: 1, ... },
//     events: { Enabled: true, MinPlayers: 1, ... }
//   }
// }

// With flatten: true (new behavior):
// {
//   sourcePlugin: 'AutoAnnounce',
//   configVersion: '2.1.0',
//   store: {                   ← Direct at root level!
//     Enabled: true,
//     MinPlayers: 1,
//     FeedBack: false,
//     Repeat: true,
//     Delay: 61,
//     Commands: [
//       'broadcast! &6Visit our store!',
//       'broadcast! &eGet 20% off today!'
//     ]
//   },
//   discord: {                 ← Direct at root level!
//     Enabled: true,
//     MinPlayers: 1,
//     FeedBack: false,
//     Repeat: true,
//     Delay: 120,
//     Commands: [
//       'broadcast! &9Join our Discord!',
//       'broadcast! &b/discord for the link'
//     ]
//   },
//   events: {                  ← Direct at root level!
//     Enabled: true,
//     MinPlayers: 1,
//     FeedBack: false,
//     Repeat: true,
//     Delay: 300,
//     Commands: [
//       'broadcast! &dUpcoming Events!',
//       'broadcast! &fCheck /events'
//     ]
//   }
// }

// Advanced: Flatten with filtering
const activeOnlyConverter = forge().forEach(
  'services',
  (service, name) => {
    // Filter out disabled services by returning null
    if (!service.enabled) {
      return null;
    }

    return {
      [name]: {
        port: service.port,
        status: 'running',
        healthCheck: `http://localhost:${service.port}/health`,
      },
    };
  },
  { flatten: true, output: 'object' }
);

// Flatten works with arrays too!
const userArrayConverter = forge().forEach(
  'users',
  (user, index) => {
    return {
      [user.id]: {
        name: user.name,
        position: index + 1,
        active: user.status === 'active',
      },
    };
  },
  { flatten: true, output: 'object' }
);

// Input: { users: [{ id: 'u1', name: 'John' }, { id: 'u2', name: 'Jane' }] }
// Output: { u1: { name: 'John', position: 1, active: true }, u2: { name: 'Jane', position: 2, active: true } }

Example 6: 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: '[email protected]'
// }

Example 6: Data Validation

const { forge, validators } = require('configforge');

// User registration form data
const formData = {
  personalInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    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: '[email protected]',
  //   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: '[email protected]',
  },
  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: '[email protected]',
//   username: 'johndoe123',
//   fullName: 'John Doe'
// }

// Check plugin results
console.log('Validation results:', result.context?.validationResults);
console.log('Backup created:', result.context?.backupPath);

Example 10: Multi-File Processing ⭐ NEW!

ConfigForge makes it easy to work with multiple files at once. Think of it like organizing your music collection - you might have individual song files that you want to combine into a playlist, or a big playlist that you want to split into smaller themed playlists.

const { forge } = require('configforge');

// Example 1: Combining Multiple Files (Many-to-One)
// Like combining individual recipe cards into a cookbook

// You have separate recipe files:
const recipeFiles = [
  {
    filename: 'chocolate-cake.yml',
    content: `
name: Chocolate Cake
category: dessert
servings: 8
ingredients:
  - flour: 2 cups
  - sugar: 1.5 cups
  - cocoa: 0.5 cups
cookTime: 45
difficulty: medium
`,
  },
  {
    filename: 'pasta-salad.yml',
    content: `
name: Pasta Salad
category: main
servings: 6
ingredients:
  - pasta: 1 lb
  - tomatoes: 2 cups
  - cheese: 1 cup
cookTime: 20
difficulty: easy
`,
  },
  {
    filename: 'fruit-smoothie.yml',
    content: `
name: Fruit Smoothie
category: drink
servings: 2
ingredients:
  - banana: 1
  - berries: 1 cup
  - yogurt: 0.5 cups
cookTime: 5
difficulty: easy
`,
  },
];

// Create a converter to combine recipes into a cookbook
const cookbookConverter = forge()
  .from('*.yml')
  .to('cookbook.yml')
  // Use filename as the recipe key (removes .yml automatically)
  .useFilenameAsKey()
  // Map recipe fields to cookbook format
  .map('name', 'title')
  .map('category', 'type')
  .map('servings', 'serves')
  .map('ingredients', 'ingredientList')
  .map('cookTime', 'prepTime')
  .map('difficulty', 'skillLevel')
  // Add some defaults for the cookbook
  .defaults({
    tested: true,
    addedDate: () => new Date().toISOString().split('T')[0], // Just the date
  });

// Convert all recipe files into one cookbook
const cookbook = await cookbookConverter.convert(recipeFiles);

console.log(
  '📚 Created cookbook with',
  Object.keys(cookbook.data).length,
  'recipes'
);
console.log(cookbook.data);
// {
//   'chocolate-cake': {
//     title: 'Chocolate Cake',
//     type: 'dessert',
//     serves: 8,
//     ingredientList: [{ flour: '2 cups' }, { sugar: '1.5 cups' }, { cocoa: '0.5 cups' }],
//     prepTime: 45,
//     skillLevel: 'medium',
//     tested: true,
//     addedDate: '2024-01-22'
//   },
//   'pasta-salad': {
//     title: 'Pasta Salad',
//     type: 'main',
//     serves: 6,
//     ingredientList: [{ pasta: '1 lb' }, { tomatoes: '2 cups' }, { cheese: '1 cup' }],
//     prepTime: 20,
//     skillLevel: 'easy',
//     tested: true,
//     addedDate: '2024-01-22'
//   },
//   'fruit-smoothie': {
//     title: 'Fruit Smoothie',
//     type: 'drink',
//     serves: 2,
//     ingredientList: [{ banana: 1 }, { berries: '1 cup' }, { yogurt: '0.5 cups' }],
//     prepTime: 5,
//     skillLevel: 'easy',
//     tested: true,
//     addedDate: '2024-01-22'
//   }
// }

// Example 2: Splitting One File into Many (One-to-Many)
// Like taking a big address book and creating separate contact cards

const addressBookData = {
  filename: 'contacts.yml',
  content: `
contacts:
  john-doe:
    name: John Doe
    email: [email protected]
    phone: 555-0123
    category: friend
    city: New York
  jane-smith:
    name: Jane Smith
    email: [email protected]
    phone: 555-0456
    category: work
    city: Boston
  mom:
    name: Mary Johnson
    email: [email protected]
    phone: 555-0789
    category: family
    city: Chicago
`,
};

// Create converter to split contacts into individual cards
const contactConverter = forge()
  .from('*.yml')
  .to('json')
  // Transform each contact
  .map('name', 'fullName')
  .map('email', 'emailAddress')
  .map('phone', 'phoneNumber')
  .map('category', 'group')
  .map('city', 'location')
  // Add some useful defaults
  .defaults({
    favorite: false,
    lastContacted: null,
  })
  // Split the contacts into separate files
  .split(data => {
    const result = {};

    // Take each contact and make it a separate file
    if (data.contacts) {
      for (const [contactId, contactInfo] of Object.entries(data.contacts)) {
        // Create filename based on contact name and category
        const filename = `${contactInfo.category}-${contactId}.yml`;
        result[filename] = contactInfo;
      }
    }

    return result;
  });

const splitContacts = await contactConverter.convert([addressBookData]);

// Get the individual contact files
const contactFiles = splitContacts.toFiles();
console.log(`📇 Split into ${contactFiles.length} contact cards:`);

contactFiles.forEach(file => {
  console.log(`📄 ${file.filename}`);
  console.log(file.content.substring(0, 100) + '...');
});

// Output:
// 📇 Split into 3 contact cards:
// 📄 friend-john-doe.yml
// fullName: John Doe
// emailAddress: [email protected]
// phoneNumber: 555-0123
// group: friend
// location: New York...
//
// 📄 work-jane-smith.yml
// fullName: Jane Smith
// emailAddress: [email protected]
// phoneNumber: 555-0456
// group: work
// location: Boston...
//
// 📄 family-mom.yml
// fullName: Mary Johnson
// emailAddress: [email protected]
// phoneNumber: 555-0789
// group: family
// location: Chicago...

// Example 3: Handling Conflicts (When Files Have Same Names)
// Like when you have two recipes both called "cake.yml"

const conflictingFiles = [
  {
    filename: 'cake.yml',
    content: 'name: Chocolate Cake\ntype: dessert\nrating: 5',
  },
  {
    filename: 'cake.yml', // Same filename!
    content: 'name: Vanilla Cake\ntype: dessert\nrating: 4',
  },
];

// Option 1: Merge conflicts (combine the values)
const mergeConverter = forge()
  .from('*.yml')
  .to('json')
  .useFilenameAsKey()
  .onKeyConflict('merge'); // Combine conflicting values into arrays

const mergedResult = await mergeConverter.convert(conflictingFiles);
console.log('🔀 Merged conflicts:', mergedResult.data);
// {
//   cake: {
//     name: ['Chocolate Cake', 'Vanilla Cake'],  // Both names in an array
//     type: 'dessert',  // Same value, no conflict
//     rating: [5, 4]    // Both ratings in an array
//   }
// }

// Option 2: Error on conflicts (be strict)
const strictConverter = forge()
  .from('*.yml')
  .to('json')
  .useFilenameAsKey()
  .onKeyConflict('error'); // Stop and report the problem

try {
  await strictConverter.convert(conflictingFiles);
} catch (error) {
  console.log('⚠️ Conflict detected:', error.message);
  // "Key conflict detected: 'cake' appears in multiple files"
}

// Real-world example: Simple server config merger
const serverConfigs = [
  {
    filename: 'database.yml',
    content: 'host: db.example.com\nport: 5432\nname: myapp',
  },
  {
    filename: 'cache.yml',
    content: 'host: cache.example.com\nport: 6379\nttl: 3600',
  },
];

const serverConverter = forge()
  .from('*.yml')
  .to('config.yml')
  .useFilenameAsKey()
  .map('host', 'hostname')
  .map('port', 'port')
  .defaults({
    enabled: true,
    timeout: 30,
  });

const serverConfig = await serverConverter.convert(serverConfigs);
console.log('🖥️ Server configuration:', serverConfig.data);
// {
//   database: {
//     hostname: 'db.example.com',
//     port: 5432,
//     name: 'myapp',
//     enabled: true,
//     timeout: 30
//   },
//   cache: {
//     hostname: 'cache.example.com',
//     port: 6379,
//     ttl: 3600,
//     enabled: true,
//     timeout: 30
//   }
// }

Example 11: 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!
  • Multi-File Processing: Process multiple input files into single output (many-to-one) or split single input into multiple outputs (one-to-many) ⭐ NEW!
  • Split Functionality: Break single input files into multiple outputs with configurable split functions ⭐ NEW!
  • Conflict Resolution: Handle field conflicts in multi-file processing with configurable strategies (error, overwrite, merge, suffix) ⭐ NEW!
  • Enhanced Result Handling: Comprehensive result objects with getSummary(), toFiles(), and enhanced print() methods ⭐ 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!
  • Object Flattening: Remove wrapper objects with forEach(..., { flatten: true }) ⭐ NEW!
  • Smart Field Tracking: Automatically tracks fields accessed in forEach operations 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() and convert() with isRawContent option ⭐ NEW!
  • Enhanced result handling with getSummary(), toFiles(), and improved print() methods ⭐ NEW!
  • Multi-file processing with convertMany() (many-to-one) and convertSplit() (one-to-many) ⭐ NEW!
  • Complete pipeline integration with multi-file processing ⭐ NEW!
  • Multi-file conflict resolution with configurable strategies (error, overwrite, merge, suffix) ⭐ NEW!
  • Split functionality with split() method for one-to-many processing ⭐ NEW!
  • Default values with defaults() ⭐ ENHANCED!
  • Array/object processing with forEach() and mapToObject() ⭐ ENHANCED!
  • Object flattening with forEach(..., { flatten: true }) ⭐ NEW!
  • Conditional mapping with when()
  • Multi-field merging with merge()
  • Field validation with validate()
  • Lifecycle hooks with before() and after() ⭐ 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

  1. You don't need to use the Mapper class directly - just use converter.convert()
  2. Use dot notation for nested objects: 'user.profile.name'
  3. Use bracket notation for arrays: 'items[0]', 'items[1]'
  4. Transform functions get the value and context: (value, ctx) => { ... }
  5. Use conditional mapping with when() for type-specific logic: when(source => source.accountType === 'premium')
  6. Use merge() to combine multiple fields: merge(['field1', 'field2'], 'result', (a, b) => a + b)
  7. Use validation to ensure data quality: validate('email', validators.email)
  8. Combine validators with validators.all() for multiple rules
  9. Always call .end() after conditional mappings to return to the main converter
  10. Check result.errors to see validation failures
  11. Check result.unmapped to see which fields weren't mapped
  12. Use result.print() for a nice conversion report
  13. Use defaults() to provide fallback values for missing fields: defaults({ status: 'active' })
  14. Use function defaults for dynamic values: defaults({ timestamp: () => new Date().toISOString() })
  15. Use before() hooks to preprocess source data before conversion
  16. Use after() hooks to postprocess target data after conversion
  17. Hooks can be async - just use async (data) => { ... } and they'll be awaited
  18. Multiple hooks execute in order - add as many as you need for complex workflows
  19. Error handling provides helpful suggestions - when field mapping fails, you'll get suggestions for similar field names
  20. Errors include rich context - see exactly where and why conversions failed with detailed error information
  21. Use error categories to filter and handle different types of errors (validation, mapping, parsing, etc.)
  22. Create CLIs for converters - use CLIGenerator.forConverter(converter) to generate command-line interfaces
  23. Use CLI profiles - save converter configurations as profiles for reuse: cli profile save my-converter
  24. CLI supports batch processing - convert multiple files at once with pattern matching and parallel processing
  25. Use built-in plugins - leverage ValidationPlugin, AuditPlugin, and BackupPlugin for enhanced functionality
  26. Plugin hooks execute in order - plugins can intercept and modify data at different conversion stages
  27. Error hooks provide recovery - plugins can handle errors gracefully and provide fallback behavior
  28. Use batch processing for multiple files - convertBatch() processes multiple files efficiently with parallel support
  29. Enable parallel processing - set parallel: true and configure workers for faster batch processing
  30. Use incremental processing - convertIncremental() only processes files that have changed since last run
  31. Monitor performance - use PerformanceMonitor to track conversion speed, memory usage, and throughput
  32. Benchmark different approaches - use PerformanceBenchmark to compare sequential vs parallel processing
  33. Configure error strategies - use errorStrategy: 'continue' to process all files even if some fail, or 'fail-fast' to stop on first error
  34. Use forEach() with { output: 'object' } to transform arrays into keyed objects - perfect for converting indexed arrays to UUID-keyed objects
  35. forEach() with object output merges returned objects - return { [key]: value } to create dynamic keys from your data
  36. Filter with forEach() - return null from the mapping function to exclude items from the result
  37. Use result.getSummary() - get detailed statistics about conversion success, file counts, errors, and duration
  38. Use result.toFiles() - convert any result to FileObject arrays for consistent multi-file handling
  39. Enhanced print() methods - result printing now includes emojis and better formatting for easier debugging
  40. Multi-file results provide file-specific statistics - track success/failure per file in batch operations
  41. Split results include generated file lists - getSummary().generatedFiles shows all files created from split operations
  42. Pipeline integration works seamlessly with multi-file processing - all pipeline steps (parse, map, transform, validate, hooks) work correctly across multiple files
  43. Multi-file context is available in all pipeline steps - access filename, fileIndex, and other multi-file metadata in transforms and hooks
  44. Error collection spans multiple files - validation errors and processing errors are collected across all files with proper context
  45. Choose forEach output type - use { output: 'array' } (default) to maintain structure, { output: 'object' } to create keyed objects
  46. Track progress - provide progressCallback to monitor batch processing progress in real-time
  47. Cache management - use IncrementalProcessor.getCacheStats() and cleanupCache() to manage incremental processing cache
  48. Save batch results - use BatchProcessor.saveBatch() to save all conversion results to an output directory
  49. Use forge() with options - pass ForgeOptions to forge({ strict: true, parallel: true }) to configure converter behavior globally
  50. Use convertString() for raw content - when you have YAML or JSON as a string, use convertString() instead of convert() ⭐ NEW!
  51. Use convert() with isRawContent option - alternatively, use convert(stringContent, { isRawContent: true }) for string input ⭐ NEW!
  52. Format auto-detection works with strings - ConfigForge automatically detects YAML vs JSON format in string content ⭐ NEW!
  53. String input handles parsing errors gracefully - malformed YAML/JSON strings will produce helpful error messages ⭐ NEW!
  54. Use { flatten: true } to remove wrapper objects - combine with { output: 'object' } to promote nested objects to root level ⭐ NEW!
  55. Flatten only works with object output - { flatten: true } requires { output: 'object' } to prevent data loss ⭐ NEW!
  56. Flatten works with both arrays and objects - remove wrappers from any source structure type ⭐ NEW!
  57. Combine flatten with filtering - return null in forEach to exclude items, flatten still works correctly ⭐ NEW!
  58. forEach field tracking is automatic - when you access fields inside forEach callbacks, they're automatically marked as mapped in statistics ⭐ NEW!
  59. forEach with target renaming - use forEach('source', 'target', mapFn) to rename arrays/objects in output: forEach('npc', 'npcs', mapFn) ⭐ NEW!
  60. Array indices are always numbers - in forEach for arrays, the index parameter is guaranteed to be a number, so index + 1 works correctly ⭐ NEW!
  61. Object keys in forEach - for objects, forEach passes the key as the second parameter for backward compatibility ⭐ NEW!
  62. Access object keys in context - when processing objects with forEach, the key is also available in context.metadata.objectKey ⭐ NEW!
  63. Use convertMany() for many-to-one processing - merge multiple input files into a single output with convertMany(inputPaths, options) ⭐ NEW!
  64. Use convertSplit() for one-to-many processing - split a single input file into multiple outputs with convertSplit(inputPath, splitFn) ⭐ NEW!
  65. Choose merge strategies wisely - use 'merge' for object merging, 'array' for simple arrays, or 'custom' for complex logic ⭐ NEW!
  66. Custom key extractors for file naming - use keyExtractor: (filePath) => customKey to control how filenames become object keys ⭐ NEW!
  67. Multi-file processing is fully tested and stable - both convertMany() and convertSplit() have comprehensive test coverage and error handling ⭐ ENHANCED!
  68. Handle file errors gracefully - set continueOnError: true to process all files even if some fail, or false to stop on first error ⭐ NEW!
  69. Perfect for plugin conversions - multi-file processing is ideal for converting between plugin formats like DecentHolograms ↔ CMI ⭐ NEW!
  70. Use rootKey for wrapping - set rootKey: 'holograms' to wrap merged data in a parent object ⭐ NEW!
  71. Check fileResults for debugging - result.fileResults shows success/failure status for each processed file ⭐ NEW!
  72. Monitor failed files - result.failedFiles contains paths of files that couldn't be processed ⭐ NEW!
  73. Split functions control output structure - return { key1: data1, key2: data2 } from split functions to create multiple output files ⭐ NEW!
  74. Handle field conflicts in multi-file processing - use onKeyConflict() to configure how to handle conflicting field names across files ⭐ NEW!
  75. Choose conflict resolution strategy - use 'error' to fail on conflicts, 'overwrite' for last-wins, 'merge' to combine into arrays, or 'suffix' to add unique suffixes ⭐ NEW!
  76. Merge strategy combines conflicting fields - when using onKeyConflict('merge'), fields with the same name across files are combined into arrays ⭐ NEW!
  77. Custom conflict suffixes - use onKeyConflict('suffix', '_backup') to customize the suffix added to conflicting field names ⭐ NEW!
  78. Force same key for conflict testing - use useKey('fixedKey') to force all files to use the same key, creating conflicts for testing resolution strategies ⭐ NEW!
  79. Use split() for one-to-many processing - break single input files into multiple outputs with split(data => ({ file1: data.part1, file2: data.part2 })) ⭐ NEW!
  80. Split functions control output structure - return an object where keys become filenames and values become file content ⭐ NEW!
  81. Split works with all mappings and transforms - apply field mappings, transformations, defaults, and hooks to each split output ⭐ NEW!
  82. Split handles errors gracefully - if split function fails or individual conversions error, detailed error information is provided ⭐ NEW!
  83. Combine split with filename mapping - use mapFilename() to include source filename information in each split output ⭐ NEW!
  84. Many-to-one processing creates single merged file - when using .to('filename.yml') with multi-file input, toFiles() creates one merged file with that name ⭐ NEW!
  85. Legacy multi-file behavior preserved - when no target schema is specified, toFiles() creates separate files for each input (backward compatibility) ⭐ NEW!
  86. One-to-one conversion without key strategy returns direct data - single file input without .useFilenameAsKey() or .useKey() returns converted data directly, not wrapped with filename key ⭐ 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.