CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-defu

Recursively assign default properties to JavaScript objects with lightweight and fast performance

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

index.mddocs/

defu

defu is a lightweight and fast TypeScript library for recursively assigning default properties to JavaScript objects. It provides multiple merging strategies with left-most argument priority, deep object merging, array concatenation, and built-in security against object pollution.

Package Information

  • Package Name: defu
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install defu

Core Imports

import { defu, createDefu, defuFn, defuArrayFn } from "defu";

For CommonJS:

const { defu, createDefu, defuFn, defuArrayFn } = require("defu");

For default import:

import defu from "defu"; // Same as named import { defu }

Basic Usage

import { defu } from "defu";

// Basic object merging with leftmost priority
const config = defu(
  { database: { host: "localhost" }, debug: true },
  { database: { host: "prod.example.com", port: 5432 }, debug: false }
);
// Result: { database: { host: "localhost", port: 5432 }, debug: true }

// Array concatenation
const options = defu(
  { plugins: ["auth", "cache"] },
  { plugins: ["logger", "monitor"] }
);
// Result: { plugins: ["auth", "cache", "logger", "monitor"] }

Architecture

defu is built around several key patterns:

  • Leftmost Priority: Arguments provided first take precedence over later arguments
  • Deep Merging: Nested objects are recursively merged rather than replaced
  • Array Concatenation: Arrays are combined rather than overwritten
  • Security-First: Automatically skips __proto__ and constructor keys to prevent pollution
  • Immutability: Never modifies source objects, always returns new merged objects
  • Custom Merging: Extensible through custom merger functions for specialized logic

Capabilities

Standard Merging

Core deep merging functionality with leftmost argument priority and array concatenation.

/**
 * Recursively merge objects with leftmost priority
 * @param source - Primary source object (highest priority)
 * @param defaults - Default objects to merge (lower priority)
 * @returns Merged object with combined properties
 */
const defu: <Source extends Input, Defaults extends Array<Input | IgnoredInput>>(
  source: Source | IgnoredInput,
  ...defaults: Defaults
) => Defu<Source, Defaults>;

Usage Examples:

import { defu } from "defu";

// Multiple defaults with priority
const settings = defu(
  { theme: "dark" },           // Highest priority
  { theme: "light", size: 14 }, // Medium priority  
  { theme: "auto", size: 12, font: "Arial" } // Lowest priority
);
// Result: { theme: "dark", size: 14, font: "Arial" }

// Null and undefined handling
const user = defu(
  { name: null, email: "user@example.com" },
  { name: "John Doe", email: "default@example.com", role: "user" }
);
// Result: { name: "John Doe", email: "user@example.com", role: "user" }

Custom Merger Creation

Factory function to create custom defu instances with specialized merging logic.

/**
 * Create a custom defu instance with custom merger logic
 * @param merger - Optional custom merger function
 * @returns Custom defu function with specialized behavior
 */
function createDefu(merger?: Merger): <Source extends Input, Defaults extends Array<Input | IgnoredInput>>(
  source: Source,
  ...defaults: Defaults
) => Defu<Source, Defaults>;

Usage Examples:

import { createDefu } from "defu";

// Add numbers instead of replacing
const addNumbers = createDefu((obj, key, value) => {
  if (typeof obj[key] === "number" && typeof value === "number") {
    obj[key] += value;
    return true; // Indicates custom merge was applied
  }
});

const totals = addNumbers({ cost: 15, items: 3 }, { cost: 10, items: 2 });
// Result: { cost: 25, items: 5 }

// Custom object merging
const mergeArrays = createDefu((obj, key, value) => {
  if (Array.isArray(obj[key]) && Array.isArray(value)) {
    obj[key] = [...new Set([...obj[key], ...value])]; // Unique merge
    return true;
  }
});

Function-Based Value Transformation

Pre-configured defu instance that applies function transformation when source provides functions.

/**
 * Defu instance with function transformation support
 * When source provides a function and default value exists, calls function with default value
 */
const defuFn: <Source extends Input, Defaults extends Array<Input | IgnoredInput>>(
  source: Source,
  ...defaults: Defaults
) => Defu<Source, Defaults>;

Usage Examples:

import { defuFn } from "defu";

// Transform default values using functions
const config = defuFn(
  {
    timeout: (defaultTimeout) => defaultTimeout * 2,
    retries: (defaultRetries) => Math.min(defaultRetries + 2, 10),
    features: (defaultFeatures) => defaultFeatures.filter(f => f !== "deprecated")
  },
  {
    timeout: 5000,
    retries: 3, 
    features: ["auth", "cache", "deprecated", "logging"]
  }
);
// Result: { timeout: 10000, retries: 5, features: ["auth", "cache", "logging"] }

Array-Specific Function Transformation

Pre-configured defu instance that applies function transformation only to array values in defaults.

/**
 * Defu instance with array-specific function transformation
 * Applies function transformation only when default value is an array
 */  
const defuArrayFn: <Source extends Input, Defaults extends Array<Input | IgnoredInput>>(
  source: Source,
  ...defaults: Defaults
) => Defu<Source, Defaults>;

Usage Examples:

import { defuArrayFn } from "defu";

const config = defuArrayFn(
  {
    plugins: (defaultPlugins) => defaultPlugins.filter(p => p !== "legacy"),
    timeout: () => 30000 // This function won't be called (not an array default)
  },
  {
    plugins: ["auth", "legacy", "cache"], // Array: function will be called
    timeout: 5000 // Number: function ignored, kept as-is
  }
);
// Result: { plugins: ["auth", "cache"], timeout: () => 30000 }

Types

/** Base input object type for defu operations */
type Input = Record<string | number | symbol, any>;

/** Types that are ignored during merging */
type IgnoredInput = 
  | boolean 
  | number 
  | null 
  | any[] 
  | Record<never, any> 
  | undefined;

/** Custom merger function signature */
type Merger = <T extends Input, K extends keyof T>(
  object: T,
  key: keyof T,
  value: T[K],
  namespace: string,
) => any;

/** TypeScript utility for compile-time type merging */
type Defu<S extends Input, D extends Array<Input | IgnoredInput>> = 
  D extends [infer F, ...infer Rest]
    ? F extends Input
      ? Rest extends Array<Input | IgnoredInput>
        ? Defu<MergeObjects<S, F>, Rest>
        : MergeObjects<S, F>
      : F extends IgnoredInput
        ? Rest extends Array<Input | IgnoredInput>
          ? Defu<S, Rest>
          : S
        : S
    : S;

/** Internal type for merging two object types */
type MergeObjects<Destination extends Input, Defaults extends Input> = 
  Destination extends Defaults
    ? Destination
    : Omit<Destination, keyof Destination & keyof Defaults> &
        Omit<Defaults, keyof Destination & keyof Defaults> & {
          -readonly [Key in keyof Destination & keyof Defaults]: 
            Destination[Key] extends null | undefined | void
              ? Defaults[Key] extends null | undefined | void
                ? null | undefined | void
                : Defaults[Key]
              : Defaults[Key] extends null | undefined | void
                ? Destination[Key]
                : Merge<Destination[Key], Defaults[Key]>;
        };

/** Internal type for merging array types */
type MergeArrays<Destination, Source> = 
  Destination extends Array<infer DestinationType>
    ? Source extends Array<infer SourceType>
      ? Array<DestinationType | SourceType>
      : Source | Array<DestinationType>
    : Source | Destination;

/** Internal comprehensive type merger with all defu rules */
type Merge<Destination extends Input, Defaults extends Input> =
  Destination extends null | undefined | void
    ? Defaults extends null | undefined | void
      ? null | undefined | void
      : Defaults
    : Defaults extends null | undefined | void
      ? Destination
      : Destination extends Array<any>
        ? Defaults extends Array<any>
          ? MergeArrays<Destination, Defaults>
          : Destination | Defaults
        : Destination extends Function
          ? Destination | Defaults
          : Destination extends RegExp
            ? Destination | Defaults
            : Destination extends Promise<any>
              ? Destination | Defaults
              : Defaults extends Function
                ? Destination | Defaults
                : Defaults extends RegExp
                  ? Destination | Defaults
                  : Defaults extends Promise<any>
                    ? Destination | Defaults
                    : Destination extends Input
                      ? Defaults extends Input
                        ? MergeObjects<Destination, Defaults>
                        : Destination | Defaults
                      : Destination | Defaults;

Install with Tessl CLI

npx tessl i tessl/npm-defu

docs

index.md

tile.json