Recursively assign default properties to JavaScript objects with lightweight and fast performance
npx @tessl/cli install tessl/npm-defu@6.1.0defu 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.
npm install defuimport { 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 }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"] }defu is built around several key patterns:
__proto__ and constructor keys to prevent pollutionCore 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" }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;
}
});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"] }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 }/** 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;