@billdaddy/mergekit
v0.1.1
Published
Tiny, type-safe deep merge — immutable, prototype-pollution safe, with configurable array strategy. Zero dependencies.
Downloads
213
Maintainers
Readme
@billdaddy/mergekit
Tiny, type-safe deep merge — immutable, prototype-pollution safe, with a configurable array strategy. Zero dependencies.
Merging config layers — defaults ← file ← env — is the classic case where
Object.assign is too shallow and a hand-rolled recursion is one __proto__
away from a prototype-pollution
CVE. mergekit does the deep merge correctly, never mutates your inputs, and
drops dangerous keys by default — in a zero-dependency package.
import { mergeAll } from "@billdaddy/mergekit";
const config = mergeAll([defaults, fileConfig, envConfig]);
// deep-merged, new object, inputs untouched, prototype-safeWhy mergekit?
- Immutable. Returns a fresh value;
targetandsourceare never touched, and nested values are cloned so the result shares no references with its inputs. - Prototype-pollution safe.
__proto__,constructor, andprototypekeys are silently skipped — a maliciousJSON.parse('{"__proto__":…}')can't poisonObject.prototypethrough your merge. - Configurable arrays. Default
"replace", or"concat", or your own combiner (union, dedupe, index-merge — your call). - Predictable atomics.
Date,Map,Set, and class instances are treated as values (replaced), not deep-merged into something unexpected. - Typed.
merge(a, b)returnsA & B;mergeAll<Config>([...])returnsConfig. - Zero dependencies, ESM + CJS + types, ~0.6 kB min+gzip.
Install
npm install @billdaddy/mergekitmerge(target, source, options?)
import { merge } from "@billdaddy/mergekit";
merge({ a: 1, nested: { x: 1 } }, { b: 2, nested: { y: 2 } });
// { a: 1, b: 2, nested: { x: 1, y: 2 } }
merge({ a: { x: 1 } }, { a: 5 }); // { a: 5 } — mismatched kinds: source winsmergeAll(objects, options?)
Fold a list left-to-right (later entries win):
import { mergeAll } from "@billdaddy/mergekit";
interface Config { server: { port: number; host: string }; debug: boolean }
const config = mergeAll<Config>([
{ server: { port: 3000, host: "localhost" }, debug: false },
{ server: { port: 8080 } },
{ debug: true },
]);
// { server: { port: 8080, host: "localhost" }, debug: true }Array strategies
interface MergeOptions {
arrayMerge?:
| "replace" // default — source array wins
| "concat" // target items, then source items
| ((target: readonly unknown[], source: readonly unknown[]) => unknown[]);
}
merge({ a: [1, 2] }, { a: [3] }); // { a: [3] }
merge({ a: [1, 2] }, { a: [3] }, { arrayMerge: "concat" }); // { a: [1, 2, 3] }
// custom: unique union
const union = (t, s) => [...new Set([...t, ...s])];
merge({ tags: ["a"] }, { tags: ["a", "b"] }, { arrayMerge: union }); // { tags: ["a", "b"] }Pairs well with
| Need | Use |
| --- | --- |
| Deep equality / independent clone | equalkit |
| Validate the merged config into typed env | envguard |
Contributors ✨
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
License
MIT © Tung Tran
