or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

customization.mdimmutable-merging.mdindex.mdmutable-merging.mdutilities.md
tile.json

customization.mddocs/

Merge Customization

Advanced customization options for controlling merge behavior across different data types, implementing custom merge strategies, and handling complex merging scenarios.

Capabilities

Merge Options Configuration

Comprehensive options for customizing both immutable and mutable merge operations.

/**
 * Options for customizing immutable deepmerge operations
 */
interface DeepMergeOptions<
  in out M,
  MM extends Readonly<Record<PropertyKey, unknown>> = {}
> {
  /** Custom function for merging objects/records, or false to disable */
  mergeRecords?: DeepMergeMergeFunctions<M, MM>["mergeRecords"] | false;
  /** Custom function for merging arrays, or false to treat as other types */
  mergeArrays?: DeepMergeMergeFunctions<M, MM>["mergeArrays"] | false;
  /** Custom function for merging Maps, or false to treat as other types */
  mergeMaps?: DeepMergeMergeFunctions<M, MM>["mergeMaps"] | false;
  /** Custom function for merging Sets, or false to treat as other types */
  mergeSets?: DeepMergeMergeFunctions<M, MM>["mergeSets"] | false;
  /** Custom function for merging other types (required if specified) */
  mergeOthers?: DeepMergeMergeFunctions<M, MM>["mergeOthers"];
  /** Function to update metadata during merging */
  metaDataUpdater?: (previousMeta: M | undefined, metaMeta: Readonly<Partial<MM>>) => M;
  /** Enable falling back to default merge when custom functions return undefined */
  enableImplicitDefaultMerging?: boolean;
}

/**
 * Options for customizing mutable deepmergeInto operations
 */
interface DeepMergeIntoOptions<
  in out M,
  MM extends Readonly<Record<PropertyKey, unknown>> = {}
> {
  /** Custom function for merging objects/records into target, or false to disable */
  mergeRecords?: DeepMergeMergeIntoFunctions<M, MM>["mergeRecords"] | false;
  /** Custom function for merging arrays into target, or false to treat as other types */
  mergeArrays?: DeepMergeMergeIntoFunctions<M, MM>["mergeArrays"] | false;
  /** Custom function for merging Maps into target, or false to treat as other types */
  mergeMaps?: DeepMergeMergeIntoFunctions<M, MM>["mergeMaps"] | false;
  /** Custom function for merging Sets into target, or false to treat as other types */
  mergeSets?: DeepMergeMergeIntoFunctions<M, MM>["mergeSets"] | false;
  /** Custom function for merging other types into target (required if specified) */
  mergeOthers?: DeepMergeMergeIntoFunctions<M, MM>["mergeOthers"];
  /** Function to update metadata during merging */
  metaDataUpdater?: (previousMeta: M | undefined, metaMeta: Readonly<Partial<MM>>) => M;
  /** Enable falling back to default merge when custom functions return undefined */
  enableImplicitDefaultMerging?: boolean;
}

Action Control System

Special symbols that control merge behavior within custom merge functions.

/**
 * Special values that tell deepmerge to perform certain actions
 */
const actions: {
  /** Indicates that the default merge strategy should be used */
  readonly defaultMerge: symbol;
  /** Indicates that merging should be skipped for this value */
  readonly skip: symbol;
};

/**
 * Special values that tell deepmergeInto to perform certain actions
 */
const actionsInto: {
  /** Indicates that the default merge strategy should be used */
  readonly defaultMerge: symbol;
};

Usage Examples:

import { deepmergeCustom } from "deepmerge-ts";

// Skip merging certain properties
const mergeWithSkip = deepmergeCustom({
  mergeRecords: (records, utils, meta) => {
    const result = {};
    const allKeys = utils.getKeys(records);
    
    for (const key of allKeys) {
      if (key === 'skipThis' || key.startsWith('_')) {
        return utils.actions.skip; // Skip this entire merge operation
      }
      
      const values = records
        .filter(record => utils.objectHasProperty(record, key))
        .map(record => record[key]);
      
      if (values.length > 0) {
        result[key] = utils.deepmerge(values);
      }
    }
    
    return result;
  }
});

// Use default merge for specific conditions
const conditionalMerge = deepmergeCustom({
  mergeArrays: (arrays, utils, meta) => {
    // Use default merge for arrays with fewer than 10 items
    const totalItems = arrays.reduce((sum, arr) => sum + arr.length, 0);
    if (totalItems < 10) {
      return utils.actions.defaultMerge;
    }
    
    // Custom merge for large arrays - take only first 5 from each
    return arrays.flatMap(arr => arr.slice(0, 5));
  }
});

Custom Merge Functions

Detailed interfaces for implementing custom merge strategies.

/**
 * Interface for immutable merge functions
 */
interface DeepMergeMergeFunctions<M, MM extends DeepMergeBuiltInMetaData> {
  /** Merge plain objects/records */
  mergeRecords: <
    Ts extends ReadonlyArray<Record<PropertyKey, unknown>>,
    U extends DeepMergeMergeFunctionUtils<M, MM>,
    MF extends DeepMergeMergeFunctionsURIs
  >(
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => DeepMergeRecordsDefaultHKT<Ts, MF, M> | symbol;
  
  /** Merge arrays */
  mergeArrays: <
    Ts extends ReadonlyArray<ReadonlyArray<unknown>>,
    U extends DeepMergeMergeFunctionUtils<M, MM>
  >(
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => DeepMergeArraysDefaultHKT<Ts, never, M> | symbol;
  
  /** Merge Sets */
  mergeSets: <
    Ts extends ReadonlyArray<ReadonlySet<unknown>>,
    U extends DeepMergeMergeFunctionUtils<M, MM>
  >(
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => DeepMergeSetsDefaultHKT<Ts> | symbol;
  
  /** Merge Maps */
  mergeMaps: <
    Ts extends ReadonlyArray<ReadonlyMap<unknown, unknown>>,
    U extends DeepMergeMergeFunctionUtils<M, MM>
  >(
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => DeepMergeMapsDefaultHKT<Ts> | symbol;
  
  /** Merge other types */
  mergeOthers: <
    Ts extends ReadonlyArray<unknown>,
    U extends DeepMergeMergeFunctionUtils<M, MM>
  >(
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => DeepMergeLeaf<Ts> | symbol;
}

/**
 * Interface for mutable merge functions
 */
interface DeepMergeMergeIntoFunctions<M, MM extends DeepMergeBuiltInMetaData> {
  /** Merge plain objects/records into target */
  mergeRecords: <
    Ts extends ReadonlyArray<Record<PropertyKey, unknown>>,
    U extends DeepMergeMergeIntoFunctionUtils<M, MM>
  >(
    target: Reference<Record<PropertyKey, unknown>>,
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => void | symbol;
  
  /** Merge arrays into target */
  mergeArrays: <
    Ts extends ReadonlyArray<ReadonlyArray<unknown>>,
    U extends DeepMergeMergeIntoFunctionUtils<M, MM>
  >(
    target: Reference<unknown[]>,
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => void | symbol;
  
  /** Merge Sets into target */
  mergeSets: <
    Ts extends ReadonlyArray<ReadonlySet<unknown>>,
    U extends DeepMergeMergeIntoFunctionUtils<M, MM>
  >(
    target: Reference<Set<unknown>>,
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => void | symbol;
  
  /** Merge Maps into target */
  mergeMaps: <
    Ts extends ReadonlyArray<ReadonlyMap<unknown, unknown>>,
    U extends DeepMergeMergeIntoFunctionUtils<M, MM>
  >(
    target: Reference<Map<unknown, unknown>>,
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => void | symbol;
  
  /** Merge other types into target */
  mergeOthers: <
    Ts extends ReadonlyArray<unknown>,
    U extends DeepMergeMergeIntoFunctionUtils<M, MM>
  >(
    target: Reference<unknown>,
    values: Ts,
    utils: U,
    meta: M | undefined
  ) => void | symbol;
}

Utility Interfaces for Merge Functions

Comprehensive utility interfaces provided to custom merge functions.

/**
 * Utility interface provided to immutable merge functions
 */
interface DeepMergeMergeFunctionUtils<M, MM extends DeepMergeBuiltInMetaData> {
  /** Default merge function implementations */
  defaultMergeFunctions: DeepMergeMergeFunctionsDefaults;
  /** Current merge function configuration */
  mergeFunctions: DeepMergeMergeFunctions<M, MM>;
  /** Metadata updater function */
  metaDataUpdater: (previousMeta: M | undefined, metaMeta: Readonly<Partial<MM>>) => M;
  /** Recursive deepmerge function for nested merging */
  deepmerge: (...objects: readonly unknown[]) => unknown;
  /** Whether implicit default merging is enabled */
  useImplicitDefaultMerging: boolean;
  /** Special action symbols */
  actions: typeof actions;
  /** Utility functions */
  getKeys: typeof getKeys;
  getObjectType: typeof getObjectType;
  objectHasProperty: typeof objectHasProperty;
}

/**
 * Utility interface provided to mutable merge functions
 */
interface DeepMergeMergeIntoFunctionUtils<M, MM extends DeepMergeBuiltInMetaData> {
  /** Default merge into function implementations */
  defaultMergeFunctions: DeepMergeMergeIntoFunctionsDefaults;
  /** Current merge into function configuration */
  mergeFunctions: DeepMergeMergeIntoFunctions<M, MM>;
  /** Metadata updater function */
  metaDataUpdater: (previousMeta: M | undefined, metaMeta: Readonly<Partial<MM>>) => M;
  /** Recursive deepmergeInto function for nested merging */
  deepmergeInto: (target: object, ...objects: readonly unknown[]) => void;
  /** Utility for merging unknown values into target */
  mergeUnknownsInto: (
    target: Reference<unknown>,
    values: ReadonlyArray<unknown>,
    utils: DeepMergeMergeIntoFunctionUtils<M, MM>,
    meta: M | undefined
  ) => void;
  /** Whether implicit default merging is enabled */
  useImplicitDefaultMerging: boolean;
  /** Special action symbols */
  actions: typeof actionsInto;
  /** Utility functions */
  getKeys: typeof getKeys;
  getObjectType: typeof getObjectType;
  objectHasProperty: typeof objectHasProperty;
}

Advanced Customization Examples

Comprehensive examples demonstrating complex customization scenarios.

Custom Array Merging Strategies:

import { deepmergeCustom, deepmergeIntoCustom } from "deepmerge-ts";

// Strategy 1: Replace arrays instead of concatenating
const replaceArrays = deepmergeCustom({
  mergeArrays: (arrays) => arrays[arrays.length - 1]
});

// Strategy 2: Merge arrays as sets (remove duplicates)
const mergeArraysAsSet = deepmergeCustom({
  mergeArrays: (arrays) => {
    const combined = arrays.flat();
    return Array.from(new Set(combined));
  }
});

// Strategy 3: Interleave arrays
const interleaveArrays = deepmergeCustom({
  mergeArrays: (arrays) => {
    if (arrays.length === 0) return [];
    
    const maxLength = Math.max(...arrays.map(arr => arr.length));
    const result = [];
    
    for (let i = 0; i < maxLength; i++) {
      for (const array of arrays) {
        if (i < array.length) {
          result.push(array[i]);
        }
      }
    }
    
    return result;
  }
});

// Strategy 4: Conditional array merging based on content
const smartArrayMerge = deepmergeCustom({
  mergeArrays: (arrays, utils, meta) => {
    // If all arrays contain numbers, sum duplicates
    const allNumbers = arrays.every(arr => 
      arr.every(item => typeof item === 'number')
    );
    
    if (allNumbers) {
      const numberMap = new Map<number, number>();
      for (const array of arrays) {
        for (const num of array as number[]) {
          numberMap.set(num, (numberMap.get(num) || 0) + 1);
        }
      }
      return Array.from(numberMap.entries()).map(([num, count]) => 
        count > 1 ? num * count : num
      );
    }
    
    // Default concatenation for other types
    return arrays.flat();
  }
});

Custom Object Merging with Validation:

import { deepmergeCustom, actions } from "deepmerge-ts";

// Validate and merge objects with schema enforcement
const schemaValidatedMerge = deepmergeCustom({
  mergeRecords: (records, utils, meta) => {
    const result = {};
    const allKeys = utils.getKeys(records);
    
    for (const key of allKeys) {
      const values = records
        .filter(record => utils.objectHasProperty(record, key))
        .map(record => record[key]);
      
      // Validate key naming convention
      if (typeof key === 'string' && key.startsWith('__')) {
        console.warn(`Skipping private property: ${key}`);
        continue;
      }
      
      // Type validation
      const types = values.map(v => typeof v);
      const uniqueTypes = new Set(types);
      
      if (uniqueTypes.size > 1) {
        console.warn(`Type mismatch for key ${String(key)}: ${Array.from(uniqueTypes).join(', ')}`);
        // Use the last value's type as authoritative
        result[key] = values[values.length - 1];
      } else if (values.length === 1) {
        result[key] = values[0];
      } else {
        // Recursively merge if all values are objects
        const firstType = utils.getObjectType(values[0]);
        if (firstType === utils.ObjectType.RECORD) {
          result[key] = utils.deepmerge(values);
        } else {
          result[key] = values[values.length - 1];
        }
      }
    }
    
    return result;
  }
});

Metadata-Driven Merging:

import { deepmergeCustom } from "deepmerge-ts";

interface MergeMetadata {
  path: string[];
  depth: number;
  strategy?: 'replace' | 'merge' | 'concat';
}

const metadataDrivenMerge = deepmergeCustom<unknown, {}, MergeMetadata>({
  metaDataUpdater: (previousMeta, metaMeta) => ({
    path: previousMeta?.path || [],
    depth: (previousMeta?.depth || 0) + 1,
    strategy: metaMeta.strategy || previousMeta?.strategy || 'merge'
  }),
  
  mergeRecords: (records, utils, meta) => {
    const strategy = meta?.strategy || 'merge';
    
    if (strategy === 'replace') {
      return records[records.length - 1];
    }
    
    const result = {};
    const allKeys = utils.getKeys(records);
    
    for (const key of allKeys) {
      const values = records
        .filter(record => utils.objectHasProperty(record, key))
        .map(record => record[key]);
      
      if (values.length > 0) {
        const childMeta = utils.metaDataUpdater(meta, {
          path: [...(meta?.path || []), String(key)],
          key,
          values
        });
        
        result[key] = utils.deepmerge(values, childMeta);
      }
    }
    
    return result;
  },
  
  mergeArrays: (arrays, utils, meta) => {
    const strategy = meta?.strategy || 'concat';
    
    switch (strategy) {
      case 'replace':
        return arrays[arrays.length - 1];
      case 'merge':
        // Merge arrays as if they were objects with numeric keys
        const merged = {};
        arrays.forEach((array, arrayIndex) => {
          array.forEach((item, itemIndex) => {
            const key = `${arrayIndex}-${itemIndex}`;
            merged[key] = item;
          });
        });
        return Object.values(merged);
      default:
        return arrays.flat();
    }
  }
}, { path: [], depth: 0 });

// Usage with metadata
const result = metadataDrivenMerge(
  { 
    config: { timeout: 1000 },
    items: [1, 2, 3] 
  },
  { 
    config: { retries: 3 },
    items: [4, 5, 6] 
  }
);

Performance-Optimized Custom Merging:

import { deepmergeIntoCustom } from "deepmerge-ts";

// High-performance mutable merging for large datasets
const performanceMerge = deepmergeIntoCustom({
  mergeRecords: (target, records, utils, meta) => {
    // Batch updates to reduce overhead
    const updates = new Map();
    
    // Collect all updates first
    for (const record of records) {
      for (const [key, value] of Object.entries(record)) {
        updates.set(key, value);
      }
    }
    
    // Apply updates in batch
    for (const [key, value] of updates) {
      if (typeof value === 'object' && value !== null && 
          target.value[key] && typeof target.value[key] === 'object') {
        // Recursive merge for nested objects
        utils.deepmergeInto(target.value[key], value);
      } else {
        target.value[key] = value;
      }
    }
  },
  
  mergeArrays: (target, arrays, utils, meta) => {
    // Pre-allocate space for better performance
    const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
    const currentLength = target.value.length;
    
    // Extend array to final size
    target.value.length = currentLength + totalLength;
    
    // Copy elements in chunks for better memory access patterns
    let insertIndex = currentLength;
    for (const array of arrays) {
      for (let i = 0; i < array.length; i++) {
        target.value[insertIndex++] = array[i];
      }
    }
  },
  
  mergeMaps: (target, maps, utils, meta) => {
    // Batch Map updates
    for (const map of maps) {
      for (const [key, value] of map) {
        target.value.set(key, value);
      }
    }
  },
  
  mergeSets: (target, sets, utils, meta) => {
    // Batch Set updates
    for (const set of sets) {
      for (const value of set) {
        target.value.add(value);
      }
    }
  }
});

Metadata System

Advanced metadata handling for context-aware merging.

/**
 * Built-in metadata type for merge operations
 */
type DeepMergeBuiltInMetaData = Readonly<Record<PropertyKey, unknown>>;

/**
 * Metadata updater function signature
 */
type MetaDataUpdater<
  in out M,
  MM extends DeepMergeBuiltInMetaData = DeepMergeBuiltInMetaData,
> = (previousMeta: M | undefined, metaMeta: Readonly<Partial<MM>>) => M;

/**
 * Default metadata updater implementation
 */
const defaultMetaDataUpdater: MetaDataUpdater<DeepMergeBuiltInMetaData>;

Custom Metadata Usage:

interface CustomMetadata {
  operationId: string;
  timestamp: number;
  mergeCount: number;
  validationRules?: string[];
}

const metadataAwareMerge = deepmergeCustom<unknown, {}, CustomMetadata>({
  metaDataUpdater: (previousMeta, metaMeta) => ({
    operationId: metaMeta.operationId || previousMeta?.operationId || crypto.randomUUID(),
    timestamp: Date.now(),
    mergeCount: (previousMeta?.mergeCount || 0) + 1,
    validationRules: metaMeta.validationRules || previousMeta?.validationRules
  }),
  
  mergeRecords: (records, utils, meta) => {
    console.log(`Merge operation ${meta?.operationId} at depth ${meta?.mergeCount}`);
    
    if (meta?.validationRules?.includes('no-private-keys')) {
      // Filter out private keys during merge
      const filteredRecords = records.map(record => {
        const filtered = {};
        for (const [key, value] of Object.entries(record)) {
          if (!key.startsWith('_')) {
            filtered[key] = value;
          }
        }
        return filtered;
      });
      
      return utils.defaultMergeFunctions.mergeRecords(filteredRecords, utils, meta);
    }
    
    return utils.defaultMergeFunctions.mergeRecords(records, utils, meta);
  }
}, {
  operationId: 'initial',
  timestamp: Date.now(),
  mergeCount: 0,
  validationRules: ['no-private-keys']
});