deepmerge-ts
Type-safe deep merging for TypeScript objects, arrays, Maps, and Sets with a smart, high-performance merge strategy.
Repository Health
Technical Analysis
deepmerge-ts deeply merges two or more JavaScript objects while preserving accurate TypeScript types for the merged result, so you don’t lose type information or need to cast after a merge. It handles records, arrays, Maps, Sets, and circular references out of the box, and ships a fully customizable deepmergeCustom API for overriding how any of those types get combined.
Instead of the classic pairwise merge-then-merge-again strategy used by many alternatives, it merges all inputs in one smart pass, which improves both execution time and memory usage as the number of merged objects grows. For performance-critical code operating on trusted, non-circular data, deepmergeFastUnsafe variants skip circular-reference tracking, depth limits, and prototype-pollution safeguards for extra speed.
What You Get
- Type-safe merge functions (
deepmerge,deepmergeCustom,deepmergeInto,deepmergeIntoCustom) whose return types are inferred directly from the inputs. - Built-in merging support for plain records, arrays,
Map, andSet, plus safe handling of circular references. - A customizer API for overriding how records, arrays, maps, sets, circular references, or any other value type get merged.
- High-performance
deepmergeFastUnsafe/deepmergeIntoFastUnsafevariants that skip safety checks for trusted, non-circular data. - A configurable recursion depth limit (default 1000) and prototype-pollution safeguards for merging untrusted input safely.
Common Use Cases
- Merging default configuration objects with user-supplied overrides while keeping full type inference.
- Combining partial state updates (including nested arrays/Maps/Sets) in state-management or reducer logic.
- Building a deep-merge with custom array/record strategies (e.g. concatenation instead of overwrite) via
deepmergeCustom. - Merging multiple layered config sources (defaults, environment, CLI flags) safely, with depth limits guarding against malicious/untrusted input.
Under The Hood
Architecture
The core mergeUnknowns function in src/deepmerge.ts dispatches by object type (record, array, set, map, other) into type-specific merge functions, each of which first calls a possibly-user-overridden function from utils.mergeFunctions and falls back to utils.defaultMergeFunctions when the custom function signals utils.actions.defaultMerge (implemented via shouldFallbackToDefault in defaults/general.ts). The four public variants — deepmerge, deepmergeFastUnsafe, deepmergeInto, deepmergeIntoFastUnsafe — each get their own defaults module (defaults/vanilla.ts, vanilla-fast.ts, into.ts, into-fast.ts) implementing the same DeepMergeUtils/DeepMergeIntoUtils contract, so the dispatch core is shared while behavior only diverges in the default merge functions and how utils gets constructed. This is a clean strategy-pattern layout: swapping defaultMergeFunctions is essentially the only thing that changes between merge modes.
Tech Stack
The library has zero runtime dependencies. It’s written in strict TypeScript targeting ESNext, built into a dual CJS/ESM package via Rollup with a dedicated TypeScript plugin and bundled declaration generation, and published to both npm and JSR from the same source. The repo uses a pnpm workspace with a separate benchmark package, splits type-checking into root and src tsconfigs (both strict: true), and tests with Vitest plus tsd for compile-time type-assertion tests. Linting layers ESLint (flat config, extensive plugin set), markdownlint, cspell, knip, and publint, with releases fully automated through semantic-release and Conventional-Commit enforcement via Husky and commitlint.
Code Quality
Test coverage is extensive — thousands of lines across dedicated test files for each merge variant, plus .test-d.ts files asserting the exact inferred type of merge results via tsd, not just runtime behavior. CI runs separate workflows for JS tests with coverage, type tests, general typechecking, and multiple lint passes, backed by pre-commit hooks. Naming conventions flag mutation explicitly (a mut_ prefix on mutable parameters like mut_target), and the library favors returning depth-limited or cyclic-safe results over throwing, relying on TypeScript’s type system and explicit guards rather than runtime exceptions for correctness.
What Makes It Unique Its core differentiator against classic deep-merge libraries is twofold: type-accurate merging, where the return type of a merge call is inferred to reflect exactly what was combined, and a “smart merge” strategy that looks across all inputs at once instead of reducing them pairwise — improving performance on large input counts and enabling behavior a pairwise reduce can’t easily express. Paired with distinct immutable/in-place APIs and separate audited-vs-unaudited code paths for the same operations, this is a deliberately differentiated design within a well-trodden problem space.
Used by 3 apps in this directory
Activepieces
Automation · AI Assistants
Open-source AI automation platform that converts 280+ workflow integrations into MCP servers for LLMs, with no-code builders and TypeScript extensibility.
authentik
Authentication · Security
The self-hosted Identity Provider that replaces Okta, Auth0, and Entra ID with a unified SSO platform supporting SAML, OAuth2/OIDC, LDAP, RADIUS, and WebAuthn.
Tabby
AI Code Assistants
Self-hosted AI coding assistant — run GitHub Copilot-grade code completion on your own hardware with no cloud dependency.