javascript

exercises

exercises.js
// ╔══════════════════════════════════════════════════════════════════════════════╗
// ║                    ES MODULES SYNTAX - EXERCISES                              ║
// ║                Practice Named, Default & Dynamic Imports                       ║
// ╚══════════════════════════════════════════════════════════════════════════════╝

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│                              EXERCISE OVERVIEW                                   │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  Exercise 1: Named Exports - Create a math utilities module                     │
│  Exercise 2: Default Export - Build a configuration module                      │
│  Exercise 3: Mixed Exports - Create a validation library                        │
│  Exercise 4: Dynamic Imports - Implement lazy loading                           │
│  Exercise 5: Barrel File - Aggregate multiple modules                           │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 1: Named Exports - Math Utilities
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Create a math utilities module with named exports                              │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Export constant: PI (3.14159)                                               │
│  2. Export constant: E (2.71828)                                                │
│  3. Export function: circleArea(radius) - returns π * r²                        │
│  4. Export function: circumference(radius) - returns 2 * π * r                  │
│  5. Export function: sphereVolume(radius) - returns (4/3) * π * r³              │
│  6. Export function: toDegrees(radians) - converts radians to degrees           │
│  7. Export function: toRadians(degrees) - converts degrees to radians           │
│                                                                                  │
│  Use inline exports for some and an export list for others.                     │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Your code here:

// ─── Solution ──────────────────────────────────────────────────────────────────

// Inline named exports
const PI = 3.14159;
const E = 2.71828;

function circleArea(radius) {
  return PI * radius * radius;
}

function circumference(radius) {
  return 2 * PI * radius;
}

function sphereVolume(radius) {
  return (4 / 3) * PI * Math.pow(radius, 3);
}

// Functions to export via list
function toDegrees(radians) {
  return radians * (180 / PI);
}

function toRadians(degrees) {
  return degrees * (PI / 180);
}

// Export list at bottom
// In a real module file, uncomment these:
// export { PI, E, circleArea, circumference, sphereVolume };
// export { toDegrees, toRadians };

// Test
console.log('Exercise 1 - Math Utilities:');
console.log('  PI:', PI);
console.log('  Circle area (r=5):', circleArea(5).toFixed(2));
console.log('  Circumference (r=5):', circumference(5).toFixed(2));
console.log('  Sphere volume (r=5):', sphereVolume(5).toFixed(2));
console.log('  90° to radians:', toRadians(90).toFixed(4));
console.log('  π radians to degrees:', toDegrees(PI).toFixed(2));
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 2: Default Export - Configuration Module
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Create a configuration module with a default export                            │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Create a Config class or object as default export                           │
│  2. Include app settings: name, version, debug mode                             │
│  3. Include API settings: baseURL, timeout, retries                             │
│  4. Include feature flags: darkMode, notifications, analytics                   │
│  5. Add a get(path) method to retrieve nested values                            │
│  6. Add a set(path, value) method (for non-frozen configs)                      │
│  7. Freeze the configuration to prevent modification                            │
│                                                                                  │
│  Bonus: Also export named helpers like getEnv() and isDev()                     │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Your code here:

// ─── Solution ──────────────────────────────────────────────────────────────────

// Named helper exports
function getEnv() {
  return typeof process !== 'undefined' ? process.env?.NODE_ENV : 'development';
}

function isDev() {
  return getEnv() === 'development';
}

function isProd() {
  return getEnv() === 'production';
}

// Default export: Configuration object
const Config = {
  app: {
    name: 'My Application',
    version: '1.0.0',
    debug: isDev(),
  },

  api: {
    baseURL: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
  },

  features: {
    darkMode: true,
    notifications: true,
    analytics: !isDev(),
  },

  // Get nested value by dot notation path
  get(path) {
    return path.split('.').reduce((obj, key) => obj?.[key], this);
  },

  // Display all settings
  toString() {
    return JSON.stringify(
      {
        app: this.app,
        api: this.api,
        features: this.features,
      },
      null,
      2
    );
  },
};

// Freeze to prevent modification
Object.freeze(Config.app);
Object.freeze(Config.api);
Object.freeze(Config.features);

// In a real module file:
// export default Config;
// export { getEnv, isDev, isProd };

// Test
console.log('Exercise 2 - Configuration Module:');
console.log('  App name:', Config.get('app.name'));
console.log('  API timeout:', Config.get('api.timeout'));
console.log('  Dark mode enabled:', Config.get('features.darkMode'));
console.log('  Environment:', getEnv());
console.log('  Is development:', isDev());
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 3: Mixed Exports - Validation Library
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Create a validation library with mixed exports                                 │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Default export: Validator class with chainable validation                   │
│  2. Named exports: Individual validation functions                              │
│     - isEmail(str) - validates email format                                     │
│     - isURL(str) - validates URL format                                         │
│     - isPhone(str) - validates phone number                                     │
│     - minLength(str, min) - checks minimum length                               │
│     - maxLength(str, max) - checks maximum length                               │
│     - isNumeric(str) - checks if string is numeric                              │
│     - isAlpha(str) - checks if string is alphabetic                             │
│  3. Named export: PATTERNS object with regex patterns                           │
│                                                                                  │
│  The Validator class should allow: new Validator(value).email().minLength(5)    │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Your code here:

// ─── Solution ──────────────────────────────────────────────────────────────────

// Named export: Regex patterns
const PATTERNS = {
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  url: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,
  phone: /^[\+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/,
  numeric: /^[0-9]+$/,
  alpha: /^[a-zA-Z]+$/,
  alphanumeric: /^[a-zA-Z0-9]+$/,
};

// Named export: Individual validation functions
function isEmail(str) {
  return PATTERNS.email.test(str);
}

function isURL(str) {
  return PATTERNS.url.test(str);
}

function isPhone(str) {
  return PATTERNS.phone.test(str);
}

function minLength(str, min) {
  return str.length >= min;
}

function maxLength(str, max) {
  return str.length <= max;
}

function isNumeric(str) {
  return PATTERNS.numeric.test(str);
}

function isAlpha(str) {
  return PATTERNS.alpha.test(str);
}

function isAlphanumeric(str) {
  return PATTERNS.alphanumeric.test(str);
}

// Default export: Validator class with chainable validation
class Validator {
  constructor(value) {
    this.value = value;
    this.errors = [];
    this._isValid = true;
  }

  email(message = 'Invalid email format') {
    if (!isEmail(this.value)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  url(message = 'Invalid URL format') {
    if (!isURL(this.value)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  phone(message = 'Invalid phone format') {
    if (!isPhone(this.value)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  min(length, message = `Minimum length is ${length}`) {
    if (!minLength(this.value, length)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  max(length, message = `Maximum length is ${length}`) {
    if (!maxLength(this.value, length)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  numeric(message = 'Must be numeric') {
    if (!isNumeric(this.value)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  alpha(message = 'Must contain only letters') {
    if (!isAlpha(this.value)) {
      this.errors.push(message);
      this._isValid = false;
    }
    return this;
  }

  isValid() {
    return this._isValid;
  }

  getErrors() {
    return this.errors;
  }

  validate() {
    return {
      valid: this._isValid,
      value: this.value,
      errors: this.errors,
    };
  }
}

// Factory function for cleaner syntax
function validate(value) {
  return new Validator(value);
}

// In a real module file:
// export default Validator;
// export {
//     isEmail, isURL, isPhone,
//     minLength, maxLength,
//     isNumeric, isAlpha, isAlphanumeric,
//     PATTERNS, validate
// };

// Test
console.log('Exercise 3 - Validation Library:');
console.log('  isEmail("test@email.com"):', isEmail('test@email.com'));
console.log('  isEmail("invalid"):', isEmail('invalid'));
console.log('  isURL("https://example.com"):', isURL('https://example.com'));
console.log('  isPhone("123-456-7890"):', isPhone('123-456-7890'));
console.log('');

// Chainable validation
const emailValidation = validate('test@example.com')
  .email()
  .min(5)
  .max(50)
  .validate();
console.log('  Chainable validation for "test@example.com":');
console.log('    Valid:', emailValidation.valid);
console.log('    Errors:', emailValidation.errors);

const badValidation = validate('ab').email().min(10).validate();
console.log('  Chainable validation for "ab":');
console.log('    Valid:', badValidation.valid);
console.log('    Errors:', badValidation.errors);
console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 4: Dynamic Imports - Lazy Loading
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Implement lazy loading with dynamic imports                                    │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Create loadModule(moduleName) - dynamically loads a module                  │
│  2. Create loadRoute(routePath) - loads route-based components                  │
│  3. Create loadPolyfillIfNeeded(feature) - conditionally loads polyfills        │
│  4. Create loadWithFallback(primary, fallback) - tries primary, falls back      │
│  5. Implement a simple module cache                                             │
│  6. Add loading states and error handling                                       │
│                                                                                  │
│  Note: Since we can't actually load files, simulate the behavior.               │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Your code here:

// ─── Solution ──────────────────────────────────────────────────────────────────

// Simulated modules for demonstration
const MOCK_MODULES = {
  './math.js': {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
  },
  './utils.js': {
    capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
    reverse: (str) => str.split('').reverse().join(''),
  },
  './pages/home.js': {
    default: { name: 'HomePage', render: () => '<h1>Home</h1>' },
  },
  './pages/about.js': {
    default: { name: 'AboutPage', render: () => '<h1>About</h1>' },
  },
  './pages/contact.js': {
    default: { name: 'ContactPage', render: () => '<h1>Contact</h1>' },
  },
};

// Module cache
const moduleCache = new Map();

// Loading states
const LoadingState = {
  IDLE: 'idle',
  LOADING: 'loading',
  SUCCESS: 'success',
  ERROR: 'error',
};

// Simulated dynamic import function
async function simulateImport(path) {
  // Simulate network delay
  await new Promise((resolve) => setTimeout(resolve, 100));

  const module = MOCK_MODULES[path];
  if (!module) {
    throw new Error(`Module not found: ${path}`);
  }
  return module;
}

// 1. Load module with caching
async function loadModule(moduleName) {
  // Check cache first
  if (moduleCache.has(moduleName)) {
    console.log(`  [Cache] Returning cached: ${moduleName}`);
    return moduleCache.get(moduleName);
  }

  console.log(`  [Loading] ${moduleName}...`);

  try {
    // In real code: const module = await import(moduleName);
    const module = await simulateImport(moduleName);

    // Cache the module
    moduleCache.set(moduleName, module);
    console.log(`  [Success] Loaded: ${moduleName}`);

    return module;
  } catch (error) {
    console.log(`  [Error] Failed to load: ${moduleName}`);
    throw error;
  }
}

// 2. Load route component
async function loadRoute(routePath) {
  const routeModules = {
    '/': './pages/home.js',
    '/home': './pages/home.js',
    '/about': './pages/about.js',
    '/contact': './pages/contact.js',
  };

  const modulePath = routeModules[routePath];
  if (!modulePath) {
    throw new Error(`Unknown route: ${routePath}`);
  }

  const module = await loadModule(modulePath);
  return module.default; // Return the default export (component)
}

// 3. Conditional polyfill loading
async function loadPolyfillIfNeeded(feature) {
  const polyfillMap = {
    'Array.flat': {
      check: () => Array.prototype.flat !== undefined,
      path: './polyfills/array-flat.js',
    },
    'Object.fromEntries': {
      check: () => Object.fromEntries !== undefined,
      path: './polyfills/object-from-entries.js',
    },
    'Promise.allSettled': {
      check: () => Promise.allSettled !== undefined,
      path: './polyfills/promise-all-settled.js',
    },
  };

  const polyfill = polyfillMap[feature];
  if (!polyfill) {
    console.log(`  Unknown feature: ${feature}`);
    return false;
  }

  if (polyfill.check()) {
    console.log(`  Feature "${feature}" is natively supported`);
    return false;
  }

  console.log(`  Loading polyfill for "${feature}"...`);
  // In real code: await import(polyfill.path);
  return true;
}

// 4. Load with fallback
async function loadWithFallback(primaryPath, fallbackPath) {
  try {
    return await loadModule(primaryPath);
  } catch (primaryError) {
    console.log(`  Primary failed, trying fallback...`);
    try {
      return await loadModule(fallbackPath);
    } catch (fallbackError) {
      throw new Error(`Both primary and fallback modules failed to load`);
    }
  }
}

// 5. Batch load modules
async function loadModules(modulePaths) {
  const results = await Promise.allSettled(
    modulePaths.map((path) => loadModule(path))
  );

  return results.map((result, index) => ({
    path: modulePaths[index],
    status: result.status,
    module: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason : null,
  }));
}

// Test
console.log('Exercise 4 - Dynamic Imports:');

(async () => {
  // Load module
  const math = await loadModule('./math.js');
  console.log('  Math module add(2,3):', math.add(2, 3));

  // Load from cache
  await loadModule('./math.js');

  // Load route
  const homePage = await loadRoute('/home');
  console.log('  Route component:', homePage.name);

  // Check polyfill
  await loadPolyfillIfNeeded('Array.flat');

  console.log('');
})();

// ════════════════════════════════════════════════════════════════════════════════
// EXERCISE 5: Barrel File - Module Aggregation
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Create a barrel file (index.js) structure for a utilities package             │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Create separate "module" objects simulating different files:                │
│     - stringUtils: capitalize, trim, truncate, slugify                          │
│     - arrayUtils: chunk, flatten, unique, shuffle                               │
│     - objectUtils: pick, omit, deepClone, merge                                 │
│     - dateUtils: format, parse, addDays, diffDays                               │
│  2. Create a barrel "file" that re-exports everything                           │
│  3. Handle naming conflicts with aliases                                         │
│  4. Export utilities both individually and as namespaces                        │
│                                                                                  │
│  Goal: import { capitalize, ArrayUtils, format } from './utils'                 │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Your code here:

// ─── Solution ──────────────────────────────────────────────────────────────────

// Simulating separate module files

// --- stringUtils.js ---
const stringUtils = {
  capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
  },

  trim(str) {
    return str.trim();
  },

  truncate(str, length, suffix = '...') {
    if (str.length <= length) return str;
    return str.slice(0, length - suffix.length) + suffix;
  },

  slugify(str) {
    return str
      .toLowerCase()
      .replace(/[^\w\s-]/g, '')
      .replace(/[\s_-]+/g, '-')
      .replace(/^-+|-+$/g, '');
  },

  // Potential naming conflict
  format(str, ...args) {
    return str.replace(/{(\d+)}/g, (match, index) =>
      args[index] !== undefined ? args[index] : match
    );
  },
};

// --- arrayUtils.js ---
const arrayUtils = {
  chunk(array, size) {
    const result = [];
    for (let i = 0; i < array.length; i += size) {
      result.push(array.slice(i, i + size));
    }
    return result;
  },

  flatten(array, depth = 1) {
    return array.flat(depth);
  },

  unique(array) {
    return [...new Set(array)];
  },

  shuffle(array) {
    const result = [...array];
    for (let i = result.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [result[i], result[j]] = [result[j], result[i]];
    }
    return result;
  },
};

// --- objectUtils.js ---
const objectUtils = {
  pick(obj, keys) {
    return keys.reduce((result, key) => {
      if (key in obj) result[key] = obj[key];
      return result;
    }, {});
  },

  omit(obj, keys) {
    return Object.keys(obj)
      .filter((key) => !keys.includes(key))
      .reduce((result, key) => {
        result[key] = obj[key];
        return result;
      }, {});
  },

  deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
  },

  merge(...objects) {
    return Object.assign({}, ...objects);
  },
};

// --- dateUtils.js ---
const dateUtils = {
  // Another potential naming conflict with stringUtils.format
  format(date, pattern = 'YYYY-MM-DD') {
    const d = new Date(date);
    const year = d.getFullYear();
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    const hours = String(d.getHours()).padStart(2, '0');
    const minutes = String(d.getMinutes()).padStart(2, '0');

    return pattern
      .replace('YYYY', year)
      .replace('MM', month)
      .replace('DD', day)
      .replace('HH', hours)
      .replace('mm', minutes);
  },

  parse(dateString) {
    return new Date(dateString);
  },

  addDays(date, days) {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
  },

  diffDays(date1, date2) {
    const d1 = new Date(date1);
    const d2 = new Date(date2);
    const diffTime = Math.abs(d2 - d1);
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  },
};

// --- index.js (Barrel File) ---
// Re-export everything with conflict handling

const barrel = {
  // From stringUtils (with alias for conflict)
  capitalize: stringUtils.capitalize,
  trim: stringUtils.trim,
  truncate: stringUtils.truncate,
  slugify: stringUtils.slugify,
  formatString: stringUtils.format, // Renamed to avoid conflict

  // From arrayUtils
  chunk: arrayUtils.chunk,
  flatten: arrayUtils.flatten,
  unique: arrayUtils.unique,
  shuffle: arrayUtils.shuffle,

  // From objectUtils
  pick: objectUtils.pick,
  omit: objectUtils.omit,
  deepClone: objectUtils.deepClone,
  merge: objectUtils.merge,

  // From dateUtils (with alias for conflict)
  formatDate: dateUtils.format, // Renamed to avoid conflict
  parseDate: dateUtils.parse,
  addDays: dateUtils.addDays,
  diffDays: dateUtils.diffDays,

  // Namespace exports
  StringUtils: stringUtils,
  ArrayUtils: arrayUtils,
  ObjectUtils: objectUtils,
  DateUtils: dateUtils,
};

/*
// In a real barrel file (index.js):

// Re-export all from each module
export * from './stringUtils.js';
export * from './arrayUtils.js';
export * from './objectUtils.js';

// Handle conflicts with explicit naming
export { format as formatString } from './stringUtils.js';
export { format as formatDate } from './dateUtils.js';
export { parse as parseDate, addDays, diffDays } from './dateUtils.js';

// Namespace exports
export * as StringUtils from './stringUtils.js';
export * as ArrayUtils from './arrayUtils.js';
export * as ObjectUtils from './objectUtils.js';
export * as DateUtils from './dateUtils.js';

// Re-export default exports as named
export { default as Logger } from './Logger.js';
*/

// Test
console.log('Exercise 5 - Barrel File Exports:');

// Individual imports
console.log('  capitalize("hello"):', barrel.capitalize('hello'));
console.log('  slugify("Hello World!"):', barrel.slugify('Hello World!'));
console.log('  chunk([1,2,3,4,5], 2):', barrel.chunk([1, 2, 3, 4, 5], 2));
console.log('  unique([1,2,2,3,3,3]):', barrel.unique([1, 2, 2, 3, 3, 3]));
console.log(
  '  pick({a:1,b:2,c:3}, ["a","c"]):',
  barrel.pick({ a: 1, b: 2, c: 3 }, ['a', 'c'])
);

// Conflict resolution
console.log(
  '  formatString("Hello {0}!", "World"):',
  barrel.formatString('Hello {0}!', 'World')
);
console.log('  formatDate(new Date()):', barrel.formatDate(new Date()));

// Namespace usage
console.log(
  '  ArrayUtils.flatten([[1,2],[3,4]]):',
  barrel.ArrayUtils.flatten([
    [1, 2],
    [3, 4],
  ])
);
console.log(
  '  DateUtils.addDays(new Date(), 7):',
  barrel.DateUtils.addDays(new Date(), 7).toDateString()
);

console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// BONUS EXERCISE: Module Factory Pattern
// ════════════════════════════════════════════════════════════════════════════════

/*
┌─────────────────────────────────────────────────────────────────────────────────┐
│  Create a module that exports a factory function for creating instances        │
│                                                                                  │
│  Requirements:                                                                  │
│  1. Export a factory function as default                                        │
│  2. Export types/interfaces as named exports                                    │
│  3. Support configuration options                                               │
│  4. Return an object with methods                                               │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘
*/

// Logger factory module

// Named exports: Types and constants
const LOG_LEVELS = {
  DEBUG: 0,
  INFO: 1,
  WARN: 2,
  ERROR: 3,
  SILENT: 4,
};

const DEFAULT_OPTIONS = {
  level: 'INFO',
  prefix: '',
  timestamp: true,
  colorize: true,
};

// Default export: Factory function
function createLogger(options = {}) {
  const config = { ...DEFAULT_OPTIONS, ...options };
  const levelValue = LOG_LEVELS[config.level] ?? LOG_LEVELS.INFO;

  function formatMessage(level, message) {
    const parts = [];

    if (config.timestamp) {
      parts.push(`[${new Date().toISOString()}]`);
    }

    parts.push(`[${level}]`);

    if (config.prefix) {
      parts.push(`[${config.prefix}]`);
    }

    parts.push(message);

    return parts.join(' ');
  }

  return {
    debug(message) {
      if (levelValue <= LOG_LEVELS.DEBUG) {
        console.log(formatMessage('DEBUG', message));
      }
    },

    info(message) {
      if (levelValue <= LOG_LEVELS.INFO) {
        console.log(formatMessage('INFO', message));
      }
    },

    warn(message) {
      if (levelValue <= LOG_LEVELS.WARN) {
        console.warn(formatMessage('WARN', message));
      }
    },

    error(message) {
      if (levelValue <= LOG_LEVELS.ERROR) {
        console.error(formatMessage('ERROR', message));
      }
    },

    setLevel(level) {
      config.level = level;
    },

    child(prefix) {
      return createLogger({
        ...config,
        prefix: config.prefix ? `${config.prefix}:${prefix}` : prefix,
      });
    },
  };
}

// In a real module:
// export default createLogger;
// export { LOG_LEVELS, DEFAULT_OPTIONS };

// Test
console.log('Bonus - Logger Factory:');
const logger = createLogger({ prefix: 'App' });
logger.info('Application started');
logger.warn('Low memory warning');

const dbLogger = logger.child('Database');
dbLogger.info('Connected to database');

console.log('');

// ════════════════════════════════════════════════════════════════════════════════
// SUMMARY
// ════════════════════════════════════════════════════════════════════════════════

console.log(`
╔══════════════════════════════════════════════════════════════════════════════╗
║                   ES MODULES SYNTAX - EXERCISES COMPLETE                     ║
╠══════════════════════════════════════════════════════════════════════════════╣
║                                                                              ║
║  Completed Exercises:                                                        ║
║  ✓ Exercise 1: Named Exports (math utilities)                               ║
║  ✓ Exercise 2: Default Export (configuration module)                        ║
║  ✓ Exercise 3: Mixed Exports (validation library)                           ║
║  ✓ Exercise 4: Dynamic Imports (lazy loading)                               ║
║  ✓ Exercise 5: Barrel File (module aggregation)                             ║
║  ✓ Bonus: Module Factory Pattern                                            ║
║                                                                              ║
║  Key Takeaways:                                                              ║
║  • Named exports: export { name } or export const name                       ║
║  • Default exports: export default value (one per module)                    ║
║  • Mixed exports: Combine both for flexible APIs                            ║
║  • Dynamic imports: import() returns Promise                                 ║
║  • Barrel files: Aggregate & re-export from index.js                        ║
║  • Handle naming conflicts with aliases (as keyword)                         ║
║                                                                              ║
╚══════════════════════════════════════════════════════════════════════════════╝
`);
Exercises - JavaScript Tutorial | DeepML