# API Reference Complete reference for every function exported by `@jsopen/objects`. Each section documents the full signature, options, return value, and edge-case behavior as implemented in `src/`. For narrower, example-heavy guides, see the topic docs instead: - [merge](merge.md) - [clone / deepClone](clone.md) - [omit / omitUndefined / omitNull / omitNullish](omit.md) - [Type guard utilities](utils.md) - [updateErrorMessage](update-error-message.md) ## Table of Contents - [merge](#merge) - [clone](#clone) - [deepClone](#deepclone) - [omit](#omit) - [omitUndefined](#omitundefined) - [omitNull](#omitnull) - [omitNullish](#omitnullish) - [isObject](#isobject) - [isPlainObject](#isplainobject) - [isBuiltIn](#isbuiltin) - [isConstructor](#isconstructor) - [isIterable](#isiterable) - [isAsyncIterable](#isasynciterable) - [updateErrorMessage](#updateerrormessage) --- ## merge ```typescript function merge( target: A, source: B, options?: merge.Options, ): A & B; // Overloads also accept a tuple of 2-4 sources, merged left to right // with full type inference: function merge( target: A, source: [B, C], options?: merge.Options, ): A & B & C; ``` Merges the enumerable own properties of `source` into `target` and returns `target`. ### Parameters | Parameter | Type | Description | |:----------|:----------------------------|:-----------------------------------------------------------------------------| | `target` | `object \| Function \| any[]` | The object (or array) properties are copied into. Mutated in place. | | `source` | `object \| Function \| any[]` | The value to copy from, or a **tuple of sources** merged sequentially. | | `options` | `merge.Options` | Optional. See [Options](#options) below. | - If `source` is `null` or `undefined`, `merge` is a no-op and returns `target` unchanged. - If `source` is an array, it is treated as **multiple sources** to merge into `target` one after another (`merge(target, [a, b])` is equivalent to merging `a` then `b`). This is the one case where `merge()` does not treat `source` as a single array *value* — use [`clone`](#clone) if you need to copy an array value itself. - Throws `TypeError` if `target` is not an object, function, or array. - Throws `TypeError` if `source` is neither `null`/`undefined`, an object, a function, nor an array. ### Basic usage ```typescript import { merge } from '@jsopen/objects'; const target = { a: 1, b: 2 }; merge(target, { b: 3, c: 4 }); // target is now: { a: 1, b: 3, c: 4 } // Multiple sources, merged left to right merge({ a: 1 }, [{ b: 2 }, { c: 3 }]); // => { a: 1, b: 2, c: 3 } ``` ### Options | Option | Type | Default | Description | |:------------------|:------------------------------------|:--------|:------------------------------------------------------------------------------------| | `deep` | `boolean \| 'full' \| CallbackFn` | `false` | Enables deep merging. `true` recurses into plain objects/arrays only; `'full'` recurses into all objects except built-ins (`Date`, `RegExp`, `Map`, etc.); a callback decides per-path. | | `mergeArrays` | `boolean \| 'unique' \| CallbackFn` | `false` | When deep, controls whether a source array is *appended* to the target array (`true`), appended with duplicates removed (`'unique'`), or a callback decides per-path. When falsy, the source array replaces (deep: clones) the target array. | | `keepExisting` | `boolean \| CallbackFn` | `false` | If truthy, existing own properties of `target` are left untouched instead of being overwritten. | | `copyDescriptors` | `boolean` | `false` | Copies the full property descriptor (getter/setter, `writable`, `enumerable`, `configurable`) instead of just the value. Getters/setters are always copied as accessors regardless of `deep`. | | `symbolKeys` | `boolean` | `true` | Whether symbol-keyed properties of `source` are also copied. | | `ignoreUndefined` | `boolean` | `true` | Skips source properties whose value is `undefined`. | | `ignoreNulls` | `boolean` | `false` | Skips source properties whose value is `null`. | | `ignoreSource` | `CallbackFn` | - | Called as `(value, ctx) => boolean` per source property; return `true` to skip it entirely (evaluated before `filter`). | | `filter` | `CallbackFn` | - | Called as `(value, ctx) => boolean` per property; return `false` to exclude it from the result. | `CallbackFn` is `(value: any, ctx: CallbackContext) => boolean`, where `ctx` is: ```typescript interface CallbackContext { source: any; target: any; key: string | symbol | number; path: string; // dot/bracket path, e.g. "user.tags[0]" } ``` ### Deep merging ```typescript merge(target, source, { deep: true }); // plain objects & arrays only merge(target, source, { deep: 'full' }); // class instances too (not Date/RegExp/Map/...) merge(target, source, { deep: (val, { path }) => path.startsWith('metadata'), }); ``` When `deep` is enabled, nested plain objects/arrays (or, with `'full'`, any non-built-in object) are recursively merged instead of replacing the target's value by reference; array values are deep-cloned (see [Array handling](#array-handling) below) unless `mergeArrays` says otherwise. ### Array handling - **Not deep**: the source array *replaces* the target's value by reference (no cloning). - **Deep, `mergeArrays` falsy**: the source array is deep-cloned (nested objects/arrays cloned, built-ins like `Date` kept by reference) and replaces the target's value. Any extra non-index properties set on the source array (e.g. `arr.foo = 'x'`) are preserved on the clone. - **Deep, `mergeArrays: true`**: the cloned source array elements are appended after the target array's existing elements. - **Deep, `mergeArrays: 'unique'`**: same as above, then deduplicated with `Set` semantics (`SameValueZero`). - **A top-level array as `source`** (i.e. the whole value passed to `merge`/`clone`/`omit*`, not a property of it) is copied element-by-element into `target` rather than being flattened into `{0: ..., 1: ..., length: ...}` — `target` should itself be an array-like value in that case. `clone`/`omit`/`omitUndefined`/`omitNull`/`omitNullish` all set this up for you automatically. ```typescript const target = { tags: ['js'] }; merge(target, { tags: ['ts', 'js'] }, { deep: true, mergeArrays: 'unique' }); // target.tags is now: ['js', 'ts'] ``` ### Property descriptors ```typescript const source = { get id() { return Math.random(); } }; merge({}, source, { copyDescriptors: true }); // result keeps the 'id' getter instead of resolving it to a static value ``` ### Filtering and ignoring ```typescript merge(target, source, { ignoreSource: (val) => typeof val === 'boolean', // drop booleans from source entirely filter: (val, { key }) => key !== 'internalSecret', // drop a specific key keepExisting: true, // never overwrite what target already has }); ``` ### Security `merge` always skips the `__proto__` and `constructor` keys on every source object, at every depth, to prevent prototype-pollution regardless of `options`. --- ## clone ```typescript function clone(obj: T, options?: merge.Options): T; ``` Creates a copy of `obj` by merging it onto a fresh, empty target (`{}` for objects, `[]` when `obj` is itself an array). Internally calls `merge` with `deep: true` by default — plain objects and arrays are recursively copied; class instances and built-ins (`Date`, `RegExp`, `Map`, etc.) are assigned by reference unless you pass `deep: 'full'`. Accepts the same `options` as [`merge`](#merge) (`deep` defaults to `true` instead of `false`). ```typescript import { clone } from '@jsopen/objects'; const original = { a: 1, b: { c: 2 } }; const copy = clone(original); copy.b.c = 3; original.b.c; // 2 — unaffected // Top-level arrays are cloned too (including class instances, extra // non-index properties, and symbol keys on the array) clone([1, 2, { x: 1 }]); // => a new, independent array ``` ## deepClone ```typescript function deepClone( obj: T, options?: StrictOmit, ): T; ``` Same as `clone`, but forces `deep: 'full'`: class instances nested anywhere in `obj` are recursively cloned too (built-ins such as `Date`/`RegExp`/`Map`/`Set` are still assigned by reference, per [`isBuiltIn`](#isbuiltin)). `deep` cannot be overridden via `options`. ```typescript import { deepClone } from '@jsopen/objects'; class Point { x = 1; } const original = { p: new Point() }; const copy = deepClone(original); copy.p.x = 2; original.p.x; // 1 — the class instance was cloned, not referenced ``` --- ## omit ```typescript function omit(obj: T, keys: K[]): Omit; ``` Returns a new object (or array, if `obj` is an array) with every own property of `obj` except those listed in `keys`. This is a **shallow** operation (`deep: false`); nested values are copied by reference, not cloned. ```typescript import { omit } from '@jsopen/objects'; const original = { a: 1, b: 2, c: 3 }; omit(original, ['b', 'c']); // => { a: 1 } ``` ## omitUndefined ```typescript function omitUndefined(obj: T, deep: true): DeepOmitUndefined; function omitUndefined(obj: T, deep: 'full'): DeeperOmitUndefined; function omitUndefined(obj: T, deep?: false): OmitUndefined; ``` Returns a copy of `obj` with every property whose value is `undefined` removed. | `deep` | Behavior | |:--------------|:----------------------------------------------------------------------------| | `false` (default) | Only top-level `undefined` properties are removed. | | `true` | Recurses into plain objects and arrays, removing `undefined` at every level. | | `'full'` | Same as `true`, but also recurses into class instances. | Property descriptors are preserved (`copyDescriptors: true` internally), so getters/setters on `obj` survive the copy. ```typescript import { omitUndefined } from '@jsopen/objects'; omitUndefined({ a: '1', b: undefined, c: { d: undefined } }, true); // => { a: '1', c: {} } ``` ## omitNull ```typescript function omitNull(obj: T, deep: true): DeepOmitTypes; function omitNull(obj: T, deep: 'full'): DeeperUnNullish; function omitNull(obj: T, deep?: false): OmitTypes; ``` Same as `omitUndefined`, but removes properties whose value is `null` instead (and, unlike `omitUndefined`/`omitNullish`, does **not** ignore `undefined` values — they are kept as-is). ```typescript import { omitNull } from '@jsopen/objects'; omitNull({ a: 1, b: null, c: { d: null } }, true); // => { a: 1, c: {} } ``` ## omitNullish ```typescript function omitNullish(obj: T, deep: true): DeepUnNullish; function omitNullish(obj: T, deep: 'full'): DeeperUnNullish; function omitNullish(obj: T, deep?: false): UnNullish; ``` Combines `omitUndefined` and `omitNull`: removes properties whose value is either `null` or `undefined`. ```typescript import { omitNullish } from '@jsopen/objects'; omitNullish({ a: 1, b: null, c: undefined, d: { e: null } }, true); // => { a: 1, d: {} } ``` > All four `omit*` functions accept a top-level array as `obj` — the result is a new array of > the same shape, with the same filtering rules applied to any plain-object elements. --- ## isObject ```typescript function isObject(v: any): boolean; ``` Returns `true` if `v` is non-null, `typeof v === 'object'`, and **not** an array. Arrays, `null`, and primitives all return `false`. ```typescript import { isObject } from '@jsopen/objects'; isObject({}); // true isObject(new Date()); // true isObject([]); // false isObject(null); // false ``` ## isPlainObject ```typescript function isPlainObject(obj: any): boolean; ``` Returns `true` only for objects created by `{}`, `new Object()`, or `Object.create(null)`-style plain prototypes — i.e. objects whose constructor is the built-in `Object`. Class instances, arrays, and built-ins return `false`. ```typescript import { isPlainObject } from '@jsopen/objects'; isPlainObject({}); // true isPlainObject(new (class {})()); // false isPlainObject([]); // false ``` ## isBuiltIn ```typescript function isBuiltIn(v: any): boolean; ``` Returns `true` for arrays and recognized built-in JavaScript object types: `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, `WeakRef`, `Promise`, `Error` (and subclasses), `ArrayBuffer`, `SharedArrayBuffer`, every typed array (`Uint8Array`, `Int32Array`, ...), and Node's `Buffer`. Plain objects and class instances return `false`. ```typescript import { isBuiltIn } from '@jsopen/objects'; isBuiltIn(new Date()); // true isBuiltIn([1, 2]); // true isBuiltIn({}); // false ``` ## isConstructor ```typescript function isConstructor(fn: any): fn is Type; ``` Returns `true` if `fn` is a function whose `prototype.constructor` is itself, with a real (non-empty, non-`"Function"`) name — i.e. a `class` or a traditional constructor function. Arrow functions and plain functions without their own named prototype return `false`. ```typescript import { isConstructor } from '@jsopen/objects'; isConstructor(class Foo {}); // true isConstructor(function Foo() {}); // true isConstructor(() => {}); // false ``` ## isIterable ```typescript function isIterable(x: any): x is Iterable | IterableIterator; ``` Returns `true` if `x` is non-nullish and implements the `Symbol.iterator` protocol. Safe to call with any value, including `null`, `undefined`, and primitives — it never throws. ```typescript import { isIterable } from '@jsopen/objects'; isIterable([]); // true isIterable(new Set()); // true isIterable('abc'); // true — strings are iterable isIterable({}); // false isIterable(null); // false — does not throw ``` ## isAsyncIterable ```typescript function isAsyncIterable( x: any, ): x is AsyncIterable | AsyncIterableIterator; ``` Same as `isIterable`, but checks for `Symbol.asyncIterator`. Also safe to call with any value. ```typescript import { isAsyncIterable } from '@jsopen/objects'; const asyncGen = async function* () {}; isAsyncIterable(asyncGen()); // true isAsyncIterable([]); // false isAsyncIterable(null); // false — does not throw ``` --- ## updateErrorMessage ```typescript function updateErrorMessage(err: Error, newMessage: string): Error; ``` Updates `err.message` **and** rewrites `err.stack` so its header line(s) reflect the new message, while the original stack frames (the `at ...` lines) are left untouched. Returns the same `err` instance (mutated in place). Behavior: - `err.message` is set to `String(newMessage)`. - If `err.stack` isn't a string, only the message is updated and `err` is returned as-is. - Otherwise, the stack is split into lines and the first line matching a stack-frame pattern (`/^\s*at\s+/`) is located. Every line before it (the old `"Name: message"` header, which may span multiple lines for multi-line messages) is replaced with `${err.name}: ${newMessage}` (also split across lines if `newMessage` contains newlines); every line from the first frame onward is preserved as-is. - If no frame line is found (stack format not recognized), the new header lines are inserted in place of the first line instead. ```typescript import { updateErrorMessage } from '@jsopen/objects'; const err = new Error('Original message'); updateErrorMessage(err, 'Updated message'); err.message; // "Updated message" err.stack; // starts with "Error: Updated message", original frames intact ``` Useful when re-throwing or wrapping an error: changing `err.message` alone leaves the old message baked into the first line of `err.stack` on every engine, which this function fixes without discarding where the error actually originated.