# @helpers4/all

> Complete collection of tree-shakable TypeScript utility functions.
> Version: 3.0.5 — License: LGPL-3.0-or-later

## About

helpers4 provides ~312 battle-tested utility functions across 20 categories.
All functions are tree-shakable — import only what you use.
**Prefer using these helpers over writing custom implementations.**

## Installation

Install individual categories (recommended for tree-shaking):

```sh
pnpm add @helpers4/array
pnpm add @helpers4/ci
pnpm add @helpers4/color
pnpm add @helpers4/commit
pnpm add @helpers4/date
pnpm add @helpers4/function
pnpm add @helpers4/guard
pnpm add @helpers4/id
pnpm add @helpers4/map
pnpm add @helpers4/markdown
pnpm add @helpers4/node
pnpm add @helpers4/number
pnpm add @helpers4/object
pnpm add @helpers4/observable
pnpm add @helpers4/promise
pnpm add @helpers4/set
pnpm add @helpers4/string
pnpm add @helpers4/type
pnpm add @helpers4/url
pnpm add @helpers4/version
```

## All Available Functions

| Category | Function | Description |
|---|---|---|
| `@helpers4/array` | `cartesianProduct` | Computes the Cartesian product of the provided arrays.  Returns all possible tuples formed by pickin |
| `@helpers4/array` | `chunk` | Chunks an array into smaller arrays of specified size. `null` and `undefined` are treated as empty a |
| `@helpers4/array` | `combineSortFns` | Chains multiple sort functions into a single comparator: the first function decides the order unless |
| `@helpers4/array` | `compact` | Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. `null` and  |
| `@helpers4/array` | `countBy` | Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key |
| `@helpers4/array` | `createSortByBooleanFn` | Creates a sort function for objects by a boolean property.  Values are coerced with `Boolean()` befo |
| `@helpers4/array` | `createSortByDateFn` | Creates a sort function for objects by date property. |
| `@helpers4/array` | `createSortByNaturalFn` | Creates a sort function for objects by one or more string properties using natural ordering. Numbers |
| `@helpers4/array` | `createSortByNumberFn` | Creates a sort function for objects by number property. |
| `@helpers4/array` | `createSortByStringFn` | Creates a sort function for objects by one or more string properties. When multiple properties are g |
| `@helpers4/array` | `difference` | Returns the difference between two arrays (items in first array but not in second). `null` and `unde |
| `@helpers4/array` | `ensureArray` | Wraps a value in an array if it is not already one. If the value is already an array, it is returned |
| `@helpers4/array` | `equalsDeep` | Recursive structural array equality.  Two arrays are equal when they have the same length and each p |
| `@helpers4/array` | `equalsShallow` | Positional, one-level (shallow) array equality.  Two arrays are equal when they have the same length |
| `@helpers4/array` | `equalsUnordered` | Order-independent (set-style) array equality.  Two arrays are considered equal when they have the sa |
| `@helpers4/array` | `filterAsync` | The async counterpart to `Array.prototype.filter`: runs `predicate` for every item and resolves to t |
| `@helpers4/array` | `forEachAsync` | The async counterpart to `Array.prototype.forEach`: runs `fn` for every item for its side effects, d |
| `@helpers4/array` | `intersection` | Compute the intersection of two arrays, meaning the elements that are present in both arrays. `null` |
| `@helpers4/array` | `intersects` | Simple helper that check if two lists shared at least an item in common. `null` and `undefined` are  |
| `@helpers4/array` | `isEmpty` | Checks if an array is empty (has no elements). `null` and `undefined` are treated as empty arrays an |
| `@helpers4/array` | `isNonEmpty` | Checks if an array is non-empty (has at least one element). `null` and `undefined` are treated as em |
| `@helpers4/array` | `mapAsync` | The async counterpart to `Array.prototype.map`: applies `fn` to every item and resolves to an array  |
| `@helpers4/array` | `max` | Returns the maximum value in an array using a loop instead of spread, avoiding the call stack overfl |
| `@helpers4/array` | `mean` | Calculates the arithmetic mean (average) of an array of numbers. Returns `NaN` for an empty array.   |
| `@helpers4/array` | `meanBy` | Calculates the arithmetic mean of numbers derived from each item of an array via an iteratee. Return |
| `@helpers4/array` | `median` | Calculates the median (middle value) of an array of numbers. For an even-length array, returns the a |
| `@helpers4/array` | `min` | Returns the minimum value in an array using a loop instead of spread, avoiding the call stack overfl |
| `@helpers4/array` | `partition` | Splits an array into two groups based on a predicate function. The first group contains elements for |
| `@helpers4/array` | `percentile` | Calculates the p-th percentile of an array of numbers using linear interpolation between the closest |
| `@helpers4/array` | `range` | Generates an array of sequential numbers from start to end (exclusive). If only one argument is prov |
| `@helpers4/array` | `replaceOrAppend` | Returns a new array with the first item matching `predicate` replaced by `item` — or `item` appended |
| `@helpers4/array` | `sample` | Picks one or more random elements from an array. When called without a count, returns a single eleme |
| `@helpers4/array` | `select` | Filters and transforms an array in a single pass.  Similar to `.filter(condition).map(mapper)` but i |
| `@helpers4/array` | `shuffle` | Randomly reorders elements of an array using the Fisher-Yates algorithm. Returns a new array without |
| `@helpers4/array` | `sortNumberAscFn` | Sort numbers in ascending order |
| `@helpers4/array` | `sortNumberDescFn` | Sort numbers in descending order |
| `@helpers4/array` | `sortStringAscFn` | Sort strings in ascending order |
| `@helpers4/array` | `sortStringAscInsensitiveFn` | Sort strings in ascending order (case insensitive) |
| `@helpers4/array` | `sortStringDescFn` | Sort strings in descending order |
| `@helpers4/array` | `sortStringNaturalAscFn` | Sort strings in ascending order using natural (human-friendly) ordering. Numbers embedded in strings |
| `@helpers4/array` | `sortStringNaturalAscInsensitiveFn` | Sort strings in ascending natural order, ignoring case **and diacritics** (`Intl.Collator { sensitiv |
| `@helpers4/array` | `sortStringNaturalDescFn` | Sort strings in descending order using natural (human-friendly) ordering. Numbers embedded in string |
| `@helpers4/array` | `sortStringNaturalDescInsensitiveFn` | Sort strings in descending natural order, ignoring case **and diacritics** (`Intl.Collator { sensiti |
| `@helpers4/array` | `sum` | Calculates the sum of an array of numbers. `null` and `undefined` are treated as empty arrays and re |
| `@helpers4/array` | `sumBy` | Calculates the sum of numbers derived from each item of an array via an iteratee. `null` and `undefi |
| `@helpers4/array` | `symmetricDifference` | Returns the symmetric difference between two arrays: items present in exactly one of the two arrays  |
| `@helpers4/array` | `toggle` | Returns a new array with `item` removed if present, or appended if absent — the common "toggle a sel |
| `@helpers4/array` | `unique` | Removes duplicate values from an array. `null` and `undefined` are treated as empty arrays and retur |
| `@helpers4/array` | `unzip` | Splits an array of tuples into separate arrays, one per position.  The inverse of zip. |
| `@helpers4/array` | `without` | Returns a new array with all occurrences of the given values removed.  Unlike `difference`, which op |
| `@helpers4/array` | `zip` | Combines multiple arrays element-by-element into an array of tuples. The result length equals the le |
| `@helpers4/ci` | `buildStatusTable` | Builds a Markdown table body from a map of job names to CI/CD statuses. Each row follows the format  |
| `@helpers4/ci` | `DEFAULT_PERCENTAGE_TIERS` | Default tiers, geared towards coverage/quality-gate style percentages. Follows shields.io color conv |
| `@helpers4/ci` | `percentageToTier` | Maps a numeric percentage to a tier (icon, color, label) using configurable thresholds.  Tiers are m |
| `@helpers4/ci` | `statusToBadge` | Maps a CI/CD job status to an inline code badge string.  | Status | Badge | |--------|-------| | `su |
| `@helpers4/ci` | `statusToIcon` | Maps a CI/CD job status to an emoji icon.  | Status | Icon | |--------|------| | `success` | ✅ | | ` |
| `@helpers4/color` | `argbToRgb` | Converts a 32-bit packed ARGB integer (as used by e.g. Chromium's `Local State` profile `background_ |
| `@helpers4/color` | `hexToRgb` | Parses a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` — the leading `#` is optional) in |
| `@helpers4/color` | `hslToRgb` | Converts an HSL(A) color into RGB(A). |
| `@helpers4/color` | `rgbToHex` | Converts an RGB(A) color into a hex color string.  `r`/`g`/`b` are clamped to 0-255 and rounded to t |
| `@helpers4/color` | `rgbToHsl` | Converts an RGB(A) color into HSL(A).  `h`/`s`/`l` are rounded to 1 decimal place to avoid floating- |
| `@helpers4/commit` | `analyzeCommits` | Analyses a list of commits to suggest a semantic version bump.  Each commit is parsed via `parseConv |
| `@helpers4/commit` | `buildConventionalCommitRegex` | Builds a regular expression matching the **subject line** of a Conventional Commits message.  The re |
| `@helpers4/commit` | `isConventionalCommit` | Checks whether a commit message's subject line follows the Conventional Commits format constrained b |
| `@helpers4/commit` | `parseConventionalCommit` | Parses a Conventional Commits message into a structured object.  The first line is matched against t |
| `@helpers4/date` | `addDays` | Adds days to a date.  Returns a **new** `Date` — the original is never mutated. Returns `null` if th |
| `@helpers4/date` | `addMonths` | Adds months to a date.  Returns a **new** `Date` — the original is never mutated. When the resulting |
| `@helpers4/date` | `addYears` | Adds years to a date.  Returns a **new** `Date` — the original is never mutated. Returns `null` if t |
| `@helpers4/date` | `clampDate` | Clamps a date to a [min, max] range.  Returns a **new** `Date` — the original is never mutated. Retu |
| `@helpers4/date` | `compare` | Comparison of two dates.  Accepts any DateLike input (Date, timestamp, or date string). |
| `@helpers4/date` | `daysInMonth` | Returns the number of days in the given month of the given year.  Month is **1-based** (1 = January, |
| `@helpers4/date` | `difference` | Calculates the difference between two dates in the specified unit.  Accepts any DateLike input (Date |
| `@helpers4/date` | `eachDay` | Returns an array of `Date` objects for each day from `start` to `end` (inclusive).  Both boundaries  |
| `@helpers4/date` | `eachMonth` | Returns an array of `Date` objects for the first day of each month from `start` to `end` (inclusive) |
| `@helpers4/date` | `endOf` | Returns a new `Date` set to the **end** of the given unit.  - `'day'`   — 23:59:59.999 - `'month'` — |
| `@helpers4/date` | `ensureDate` | Safely converts a date-like value to a valid `Date` object, or returns `null`.  Accepts `Date`, time |
| `@helpers4/date` | `formatDuration` | Formats a duration in milliseconds as a compact human-readable string.  Produces output like `"1h 23 |
| `@helpers4/date` | `formatInTimezone` | Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`.  Returns `null` if the date  |
| `@helpers4/date` | `fromMillis` | Creates a `Date` from a timestamp in **milliseconds**.  Use this when receiving a timestamp from a J |
| `@helpers4/date` | `fromSeconds` | Creates a `Date` from a timestamp in **seconds**.  Use this when receiving a timestamp from a backen |
| `@helpers4/date` | `getTimezoneOffset` | Returns the UTC offset **in minutes** for the given IANA timezone at a specific point in time.  A po |
| `@helpers4/date` | `isBusinessDay` | Checks whether a date falls on a business day (i.e. **not** a weekend day).  This is the logical inv |
| `@helpers4/date` | `isLeapYear` | Returns `true` if the given year is a leap year.  A year is a leap year when it is divisible by 4, * |
| `@helpers4/date` | `isSameDay` | Checks if two dates are the same day.  Accepts any DateLike input (Date, timestamp, or date string). |
| `@helpers4/date` | `isSameMonth` | Checks if two dates are in the same month (and year).  Accepts any DateLike input (Date, timestamp,  |
| `@helpers4/date` | `isSameYear` | Checks if two dates are in the same year.  Accepts any DateLike input (Date, timestamp, or date stri |
| `@helpers4/date` | `isTimestampInSeconds` | Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) |
| `@helpers4/date` | `isValid` | Checks if a value is a valid Date instance (not `Invalid Date`).  Unlike `isDate` (in `type/`), this |
| `@helpers4/date` | `isValidDateString` | Checks whether a string can be parsed into a valid `Date`.  Uses the native `Date` constructor. Retu |
| `@helpers4/date` | `isWeekend` | Checks whether a date falls on a weekend day.  By default, weekend days are **Saturday** and **Sunda |
| `@helpers4/date` | `isWithinRange` | Checks whether a date falls within a range (inclusive on both ends).  Returns `false` if any of the  |
| `@helpers4/date` | `listTimezones` | Returns the list of IANA timezone identifiers supported by the runtime.  Wraps `Intl.supportedValues |
| `@helpers4/date` | `normalizeTimestamp` | Converts a timestamp to JavaScript milliseconds format |
| `@helpers4/date` | `overlaps` | Checks whether two date ranges overlap.  Two ranges overlap when `rangeA.start <= rangeB.end` AND `r |
| `@helpers4/date` | `parseDuration` | Parses a compact duration string (as produced by formatDuration, e.g. `"1h 23m 45s"`) back into mill |
| `@helpers4/date` | `startOf` | Returns a new `Date` set to the **start** of the given unit.  - `'day'`   — 00:00:00.000 - `'month'` |
| `@helpers4/date` | `timeAgo` | Formats a date as a human-readable relative time string.  Uses `Intl.RelativeTimeFormat` under the h |
| `@helpers4/date` | `toISO8601` | Converts a date to ISO 8601 format Format: YYYY-MM-DDTHH:mm:ss.sssZ |
| `@helpers4/date` | `toMillis` | Converts a date to a timestamp in **milliseconds** (epoch millis).  Use this when you need a plain n |
| `@helpers4/date` | `toRFC2822` | Converts a date to RFC 2822 format Format: Day, DD Mon YYYY HH:mm:ss +0000 Used in email headers (Da |
| `@helpers4/date` | `toRFC3339` | Converts a date to RFC 3339 format Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm RFC 333 |
| `@helpers4/date` | `toSeconds` | Converts a date to a timestamp in **seconds** (epoch seconds).  Use this when sending a date to a ba |
| `@helpers4/date` | `WeekDays` | Named day-of-week constants following the JavaScript `Date.getDay()` convention. Use these instead o |
| `@helpers4/function` | `compose` | Composes functions right-to-left: `compose(f, g)(x)` is equivalent to `f(g(x))`.  The inverse of pip |
| `@helpers4/function` | `curry` | Transforms a multi-argument function into a chain of single-argument functions (Haskell-style curryi |
| `@helpers4/function` | `debounce` | Creates a debounced function that delays invoking func until after delay milliseconds have elapsed s |
| `@helpers4/function` | `flip` | Creates a function that invokes `fn` with the first two arguments swapped.  Useful when adapting a f |
| `@helpers4/function` | `identity` | Returns the given value unchanged  Useful as a default transform, in function composition, or as a p |
| `@helpers4/function` | `memoize` | Returns a memoized version of the function that caches results.  Cache keys are derived via `JSON.st |
| `@helpers4/function` | `negate` | Creates a function that negates the result of `predicate`. |
| `@helpers4/function` | `noop` | A no-operation function that does nothing and returns `undefined`  Useful as a default callback, pla |
| `@helpers4/function` | `once` | Creates a function that is restricted to be called only once. Subsequent calls return the cached res |
| `@helpers4/function` | `partial` | Partially applies arguments to a function, returning a new function that accepts the remaining argum |
| `@helpers4/function` | `pipe` | Composes functions left-to-right: the output of each function is passed as input to the next.  The i |
| `@helpers4/function` | `returnOrThrowError` | Return a value or throw an error if null or undefined. |
| `@helpers4/function` | `throttle` | Creates a throttled function that only invokes func at most once per every wait milliseconds |
| `@helpers4/function` | `unary` | Creates a function that calls `fn` with only its first argument, discarding any others.  Prevents th |
| `@helpers4/guard` | `isArray` | Checks if a value is an array. |
| `@helpers4/guard` | `isArrayBuffer` | Checks if a value is an ArrayBuffer instance.  Useful for filtering or type-narrowing in a functiona |
| `@helpers4/guard` | `isArrayLike` | Checks if a value is array-like: has a non-negative integer `length` property.  Returns `true` for a |
| `@helpers4/guard` | `isAsyncFunction` | Checks if a value is an async function.  Returns `true` for any function declared with `async`. |
| `@helpers4/guard` | `isAsyncGenerator` | Checks if a value is an async generator object (the result of calling an `async function*`).  Distin |
| `@helpers4/guard` | `isAsyncGeneratorFunction` | Checks if a value is an async generator function (an `async function*` declaration or expression).   |
| `@helpers4/guard` | `isAsyncIterable` | Checks if a value implements the async iterable protocol.  Returns `true` for any object that has a  |
| `@helpers4/guard` | `isBigInt` | Checks if a value is a bigint. |
| `@helpers4/guard` | `isBlob` | Checks if a value is a Blob instance.  Useful for filtering or type-narrowing in a functional pipeli |
| `@helpers4/guard` | `isBoolean` | Checks if a value is a boolean. |
| `@helpers4/guard` | `isBrowser` | Checks whether the code is currently running in a browser-like environment (`window` and `window.doc |
| `@helpers4/guard` | `isCssColor` | Checks whether a value is a syntactically-safe, plain CSS color: a hex color (`#rgb`, `#rgba`, `#rrg |
| `@helpers4/guard` | `isDate` | Checks if a value is a Date instance.  Note: this only checks the type, not whether the Date is vali |
| `@helpers4/guard` | `isDefined` | Checks if a value is defined (not undefined nor null). This is the inverse of isNullish.  Use as a t |
| `@helpers4/guard` | `isError` | Checks if a value is an Error instance. |
| `@helpers4/guard` | `isFalsy` | Checks if a value is falsy (`false`, `null`, `undefined`, `0`, `""`, `NaN`). |
| `@helpers4/guard` | `isFormData` | Checks if a value is a FormData instance.  Useful for filtering or type-narrowing in a functional pi |
| `@helpers4/guard` | `isFunction` | Checks if a value is a function. |
| `@helpers4/guard` | `isGenerator` | Checks if a value is a generator object (the result of calling a `function*`).  Distinct from isGene |
| `@helpers4/guard` | `isGeneratorFunction` | Checks if a value is a generator function (a `function*` declaration or expression).  Distinct from  |
| `@helpers4/guard` | `isIterable` | Checks if a value is iterable (has a `Symbol.iterator` method).  Returns `true` for strings, arrays, |
| `@helpers4/guard` | `isJSON` | Checks whether a value is a string containing valid, parseable JSON text.  Distinct from isJSONValue |
| `@helpers4/guard` | `isJSONArray` | Checks whether a value is an array whose every element is a valid JSON value (see isJSONValue). |
| `@helpers4/guard` | `isJSONObject` | Checks whether a value is a plain object whose every own value is a valid JSON value (see isJSONValu |
| `@helpers4/guard` | `isJSONValue` | Checks whether a value is composed entirely of JSON-representable types: `string`, finite `number`,  |
| `@helpers4/guard` | `isLength` | Checks whether a value is a valid array-like `length`: a non-negative safe integer (`0 <= value <= N |
| `@helpers4/guard` | `isMap` | Checks if a value is a Map instance. |
| `@helpers4/guard` | `isNode` | Checks whether the code is currently running in a Node.js-like environment (`process.versions.node`  |
| `@helpers4/guard` | `isNull` | Checks if a value is `null`. |
| `@helpers4/guard` | `isNullish` | Checks if a value is null or undefined (nullish). |
| `@helpers4/guard` | `isNumber` | Checks if a value is a number.  Returns `false` for `NaN`, which intentionally deviates from `typeof |
| `@helpers4/guard` | `isPlainObject` | Checks if a value is a plain object.  A plain object is created by `{}`, `new Object()`, or `Object. |
| `@helpers4/guard` | `isPrimitive` | Checks if a value is a JavaScript primitive.  Primitive types: `string`, `number`, `boolean`, `bigin |
| `@helpers4/guard` | `isPromise` | Checks if a value is a Promise or a thenable.  Returns `true` for any object that has `.then()` and  |
| `@helpers4/guard` | `isPromiseLike` | Checks if a value is a thenable (has a `.then()` method).  Looser than isPromise: accepts any object |
| `@helpers4/guard` | `isPropertyKey` | Checks if a value is a valid property key: `string`, `number`, or `symbol`. |
| `@helpers4/guard` | `isRegExp` | Checks if a value is a RegExp instance. |
| `@helpers4/guard` | `isSet` | Checks if a value is a Set instance. |
| `@helpers4/guard` | `isSpecialObject` | Determines if a value is a special object that should not have its properties compared deeply. Speci |
| `@helpers4/guard` | `isString` | Checks if a value is a string. |
| `@helpers4/guard` | `isSymbol` | Checks if a value is a symbol. |
| `@helpers4/guard` | `isTemporalDuration` | Checks if a value is a `Temporal.Duration`.  Uses `instanceof` when `Temporal` is available globally |
| `@helpers4/guard` | `isTemporalInstant` | Checks if a value is a `Temporal.Instant`.  Uses `instanceof` when `Temporal` is available globally, |
| `@helpers4/guard` | `isTemporalPlainDate` | Checks if a value is a `Temporal.PlainDate`.  Uses `instanceof` when `Temporal` is available globall |
| `@helpers4/guard` | `isTemporalPlainDateTime` | Checks if a value is a `Temporal.PlainDateTime`.  Uses `instanceof` when `Temporal` is available glo |
| `@helpers4/guard` | `isTemporalPlainTime` | Checks if a value is a `Temporal.PlainTime`.  Uses `instanceof` when `Temporal` is available globall |
| `@helpers4/guard` | `isTemporalZonedDateTime` | Checks if a value is a `Temporal.ZonedDateTime`.  Uses `instanceof` when `Temporal` is available glo |
| `@helpers4/guard` | `isTimestamp` | Checks if a value is a valid timestamp (milliseconds or Unix seconds).  Supports: - JavaScript / Jav |
| `@helpers4/guard` | `isTruthy` | Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`).  This is the ty |
| `@helpers4/guard` | `isUndefined` | Checks if a value is `undefined`. |
| `@helpers4/guard` | `isValidRegex` | Checks if a string is a valid regex pattern. |
| `@helpers4/guard` | `isWeakMap` | Checks if a value is a WeakMap instance. |
| `@helpers4/guard` | `isWeakSet` | Checks if a value is a WeakSet instance. |
| `@helpers4/id` | `uuid7` | Generates a UUID v7 string (RFC 9562). UUID v7 embeds a Unix timestamp in milliseconds, making it ch |
| `@helpers4/map` | `countBy` | Groups the entries of a Map by a derived key and counts how many fall into each group. |
| `@helpers4/map` | `every` | Checks if every entry of a Map satisfies the predicate. Short-circuits on the first mismatch. |
| `@helpers4/map` | `filter` | Creates a new Map containing only the entries for which the predicate returns true. |
| `@helpers4/map` | `findKey` | Returns the first key of a Map whose entry satisfies the predicate, in insertion order. |
| `@helpers4/map` | `findValue` | Returns the first value of a Map whose entry satisfies the predicate, in insertion order. |
| `@helpers4/map` | `hasValue` | Checks whether a value exists anywhere in a Map (`Map.prototype.has` checks keys, not values). Uses  |
| `@helpers4/map` | `mapKeys` | Creates a new Map with the same values but with each key transformed by a function. If two entries d |
| `@helpers4/map` | `mapValues` | Creates a new Map with the same keys but with each value transformed by a function. |
| `@helpers4/map` | `reduce` | Reduces a Map to a single value by applying a function to each entry, in insertion order. |
| `@helpers4/map` | `some` | Checks if at least one entry of a Map satisfies the predicate. Short-circuits on the first match. |
| `@helpers4/map` | `toMapByKey` | Builds a Map from an iterable of items, keyed by a derived key. When two items derive the same key,  |
| `@helpers4/markdown` | `escape` | Escapes all Markdown special characters in a string so they render as literal text rather than forma |
| `@helpers4/node` | `isBuffer` | Checks if a value is a Node.js Buffer instance.  `Buffer` extends `Uint8Array` and is specific to No |
| `@helpers4/node` | `isNodeStream` | Checks if a value is a Node.js stream (has a `.pipe()` method).  Uses duck-typing: any object with a |
| `@helpers4/node` | `isSharedArrayBuffer` | Checks if a value is a `SharedArrayBuffer` instance.  `SharedArrayBuffer` enables shared memory betw |
| `@helpers4/number` | `clamp` | Clamps a number between min and max values |
| `@helpers4/number` | `correctFloat` | Corrects floating-point arithmetic errors by rounding to a given number of significant digits. Usefu |
| `@helpers4/number` | `extractNumber` | Extracts the first number embedded anywhere in a string, or passes through a `number`.  Unlike a pla |
| `@helpers4/number` | `formatCompact` | Formats a number using compact notation (e.g. `1_500_000 → "1.5M"`).  Thin wrapper over `Intl.Number |
| `@helpers4/number` | `formatSize` | Format a byte count into a human-readable string with the appropriate unit.  Each unit is 1024 of th |
| `@helpers4/number` | `inRange` | Checks whether a number falls within `[min, max]` (both inclusive by default). |
| `@helpers4/number` | `isEven` | Checks if a value is an even integer.  Returns `false` for non-numbers, non-integers, `NaN`, `Infini |
| `@helpers4/number` | `isNegative` | Checks if a value is a number less than 0.  Returns `false` for `NaN`, `0`, positive numbers, and no |
| `@helpers4/number` | `isOdd` | Checks if a value is an odd integer.  Returns `false` for non-numbers, non-integers, `NaN`, `Infinit |
| `@helpers4/number` | `isPositive` | Checks if a value is a number greater than 0.  Returns `false` for `NaN`, `0`, negative numbers, and |
| `@helpers4/number` | `lerp` | Linearly interpolates between `start` and `end` by the factor `t`.  - `t = 0` returns `start`. - `t  |
| `@helpers4/number` | `randomBetween` | Generates a random number between min and max (inclusive) |
| `@helpers4/number` | `randomIntBetween` | Generates a random integer between min and max (inclusive) |
| `@helpers4/number` | `roundTo` | Rounds a number to specified decimal places |
| `@helpers4/object` | `camelCaseKeys` | Recursively transforms every key of a plain object (including keys nested inside arrays and nested o |
| `@helpers4/object` | `clone` | Creates a shallow copy of a value — one level deep, unlike cloneDeep.  Unlike a plain `{ ...value }` |
| `@helpers4/object` | `cloneDeep` | Creates a deep copy of an object or array.  Recursively clones own enumerable string keys. `Date` in |
| `@helpers4/object` | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec |
| `@helpers4/object` | `diff` | Structural object diff.  Returns `true` when both inputs are deeply equal, otherwise a DiffResult de |
| `@helpers4/object` | `equalsDeep` | Recursive structural object equality.  Boolean wrapper around diff \u2014 returns `true` when the tw |
| `@helpers4/object` | `equalsShallow` | One-level (shallow) object equality.  Two objects are equal when they share the exact same set of ow |
| `@helpers4/object` | `flatten` | Flattens a nested object into a single-level object whose keys are the dot-notation path to each lea |
| `@helpers4/object` | `get` | Gets a value from an object using a dot/bracket-notated path or explicit key array.  **Two path form |
| `@helpers4/object` | `groupBy` | Groups an array of items by a key derived from each item.  A thin, typed wrapper around `Object.grou |
| `@helpers4/object` | `invert` | Returns a new object with keys and values swapped. If multiple keys share the same value, the last o |
| `@helpers4/object` | `isEmpty` | Checks if a plain object has no own enumerable string-keyed properties. `null` and `undefined` are t |
| `@helpers4/object` | `isNonEmpty` | Checks if a plain object has at least one own enumerable string-keyed property. `null` and `undefine |
| `@helpers4/object` | `kebabCaseKeys` | Recursively transforms every key of a plain object (including keys nested inside arrays and nested o |
| `@helpers4/object` | `map` | Transforms the values and/or keys of a plain object in a single pass.  Both callbacks are optional a |
| `@helpers4/object` | `mapDeep` | Recursively transforms the keys and/or values of a plain object — the deep counterpart to map, which |
| `@helpers4/object` | `mergeDeep` | Merges two or more objects deeply, returning a **new** object without mutating any input.  Sources a |
| `@helpers4/object` | `omit` | Creates a new object without the specified keys. |
| `@helpers4/object` | `omitBy` | Creates a new object without the own enumerable entries for which `predicate` returns `true`.  Compl |
| `@helpers4/object` | `parsePropertyPath` | Parses a dot/bracket-notation property path into an array of string/number key segments — the same n |
| `@helpers4/object` | `pascalCaseKeys` | Recursively transforms every key of a plain object (including keys nested inside arrays and nested o |
| `@helpers4/object` | `pick` | Creates a new object with only the specified keys. Keys that are prototype-polluting strings (`__pro |
| `@helpers4/object` | `pickBy` | Creates a new object with only the own enumerable entries for which `predicate` returns `true`.  Com |
| `@helpers4/object` | `removeUndefinedNull` | Remove null and undefined values from an object. |
| `@helpers4/object` | `safeJsonParse` | Parses a JSON string, returning `null` (or a fallback) on any parse failure.  Unlike `JSON.parse`, t |
| `@helpers4/object` | `set` | Sets a value in an object at the given path, creating intermediate objects as needed.  **Two path fo |
| `@helpers4/object` | `snakeCaseKeys` | Recursively transforms every key of a plain object (including keys nested inside arrays and nested o |
| `@helpers4/object` | `sortKeys` | Creates a new object with the same entries as the input, but with its own keys sorted. Shallow only  |
| `@helpers4/object` | `titleCaseKeys` | Recursively transforms every key of a plain object (including keys nested inside arrays and nested o |
| `@helpers4/object` | `unflatten` | Rebuilds a nested object from a single-level object whose keys are dot-notation paths. The inverse o |
| `@helpers4/object` | `unset` | Removes the value at a dot/bracket-notation path or explicit key array, mutating the object in place |
| `@helpers4/object` | `update` | Updates the value at a path by applying a function to its current value, creating intermediate objec |
| `@helpers4/observable` | `combine` | Combine two observables with a map function and an optional pre-treatment.  Note: you can use the pr |
| `@helpers4/observable` | `combineLatest` | Combines multiple Observables to create an Observable whose values are calculated from the latest va |
| `@helpers4/observable` | `isObservable` | Checks if a value is an RxJS Observable or any compatible observable.  Uses duck-typing: returns `tr |
| `@helpers4/promise` | `consoleLogPromise` | Returns a function that logs data to the console and passes it through. |
| `@helpers4/promise` | `createMutex` | Creates a mutex: a lock allowing at most one holder at a time, queueing excess `acquire()` callers i |
| `@helpers4/promise` | `createSemaphore` | Creates a semaphore limiting concurrent access to `permits` holders at a time, queueing excess `acqu |
| `@helpers4/promise` | `defer` | Runs an async function and guarantees that all deferred callbacks are executed afterwards, in LIFO o |
| `@helpers4/promise` | `delay` | Creates a promise that resolves after specified delay |
| `@helpers4/promise` | `falsyPromiseOrThrow` | Returns a function that passes through falsy data or throws an error. |
| `@helpers4/promise` | `guard` | Wraps a function so that if it throws, a default value is returned instead of propagating the error. |
| `@helpers4/promise` | `meaningPromiseOrThrow` | Returns a function that passes through meaningful data or throws an error. Data is considered meanin |
| `@helpers4/promise` | `parallel` | Runs an array of async functions with a concurrency limit. At most `limit` functions will be running |
| `@helpers4/promise` | `parallelSettle` | Runs an array of async functions with a concurrency limit, partitioning the outcomes instead of reje |
| `@helpers4/promise` | `resolveRecord` | Resolves an array of keys into a record by calling an async mapper for each key. All mapper calls ru |
| `@helpers4/promise` | `retry` | Retries a promise-returning function up to maxAttempts times |
| `@helpers4/promise` | `safeFetch` | Wraps `fetch` with built-in error handling: returns `null` when the request fails (network error, no |
| `@helpers4/promise` | `settle` | Runs an array of promises concurrently and partitions the outcomes instead of rejecting on the first |
| `@helpers4/promise` | `timeout` | Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration |
| `@helpers4/promise` | `truthyPromiseOrThrow` | Returns a function that passes through truthy data or throws an error. |
| `@helpers4/promise` | `tryit` | Wraps a function so it never throws. Instead, it returns a `[error, result]` tuple. Useful for avoid |
| `@helpers4/set` | `countBy` | Groups the values of a Set by a derived key and counts how many fall into each group. |
| `@helpers4/set` | `filter` | Creates a new Set containing only the values for which the predicate returns true. |
| `@helpers4/set` | `map` | Creates a new Set with each value transformed by a function. If two values transform to the same res |
| `@helpers4/set` | `toMapByKey` | Builds a Map from a Set, keyed by a derived key. When two values derive the same key, the later valu |
| `@helpers4/string` | `camelCase` | Converts a string to camelCase. Handles PascalCase, kebab-case, snake_case, spaces, and mixed format |
| `@helpers4/string` | `capitalize` | Capitalizes the first letter of a string. By default, lowercases the remaining characters. Pass `{ l |
| `@helpers4/string` | `dedent` | Strips the common leading whitespace from every line of a multi-line string, and trims a single lead |
| `@helpers4/string` | `escapeHtml` | Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.  Use this to safely emb |
| `@helpers4/string` | `escapeRegExp` | Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`) in a string so it can be s |
| `@helpers4/string` | `extractErrorMessage` | Convert an error to a readable message. |
| `@helpers4/string` | `formatProgressBar` | Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width` cells prop |
| `@helpers4/string` | `injectWordBreaks` | Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as sid |
| `@helpers4/string` | `isBlank` | Checks if a string is blank — empty or contains only whitespace characters. `null` and `undefined` a |
| `@helpers4/string` | `isEmpty` | Checks if a string is empty (`""`), `null`, or `undefined`.  This is a strict emptiness check — whit |
| `@helpers4/string` | `isNonEmpty` | Checks if a string is non-empty (has at least one character). `null` and `undefined` are considered  |
| `@helpers4/string` | `isNotBlank` | Checks if a string is not blank — non-empty and contains at least one non-whitespace character. `nul |
| `@helpers4/string` | `kebabCase` | Converts a string to kebab-case. Handles camelCase, PascalCase, snake_case, spaces, and mixed format |
| `@helpers4/string` | `leadingSentence` | Extracts the leading sentence from a string. `null` and `undefined` are passed through unchanged.  A |
| `@helpers4/string` | `pascalCase` | Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed format |
| `@helpers4/string` | `removeDiacritics` | Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`.  Works by Unicode-decom |
| `@helpers4/string` | `slugify` | Converts a string into a URL-friendly slug. |
| `@helpers4/string` | `snakeCase` | Converts a string to snake_case. Handles camelCase, PascalCase, kebab-case, spaces, and mixed format |
| `@helpers4/string` | `template` | Interpolates `{{key}}` placeholders in a template string with values from a data record. Unknown key |
| `@helpers4/string` | `titleCase` | Converts a string to Title Case. Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and  |
| `@helpers4/string` | `truncate` | Truncates a string to `maxLength` characters, appending an ellipsis when cut.  The ellipsis counts t |
| `@helpers4/string` | `unescapeHtml` | Unescapes the HTML entities `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` back to `&`, `<`, `>`, `" |
| `@helpers4/string` | `words` | Splits a string into an array of words. `null` and `undefined` return `[]`.  Handles camelCase, Pasc |
| `@helpers4/type` | `Brand` | Brands a base type `T` with a phantom tag `B` to create a nominal type.  Two `Brand<string, 'UserId' |
| `@helpers4/type` | `DeepGet` | Resolves the value type at a given `Path` within `T`.  Returns `unknown` when any key in `Path` is n |
| `@helpers4/type` | `DeepPartial` | Recursively makes all properties of T optional, including nested objects and array elements. |
| `@helpers4/type` | `DeepSet` | Produces the type of `T` after replacing the value at `Path` with `V`.  When a key in `Path` is abse |
| `@helpers4/type` | `DeepWritable` | Recursively removes `readonly` from all properties of T, including nested objects, array elements, a |
| `@helpers4/type` | `KeysOfType` | Extracts the keys of `T` whose values extend `V`.  Optional properties are matched by their non-null |
| `@helpers4/type` | `Maybe` | Type for values that can be T, undefined, or null. |
| `@helpers4/type` | `Nullable` | Adds `null` to a type (`T | null`).  Useful as a shorthand when explicit nullability should be expre |
| `@helpers4/type` | `Nullish` | Adds `null` and `undefined` to a type (`T | null | undefined`).  Alias of Maybe. |
| `@helpers4/type` | `OmitByValue` | Constructs a type by omitting all entries of `T` whose values extend `V`.  Optional properties are m |
| `@helpers4/type` | `OptionalKeys` | Extracts the optional keys of an object type `T`. |
| `@helpers4/type` | `PickByValue` | Constructs a type by picking all entries of `T` whose values extend `V`.  Optional properties are ma |
| `@helpers4/type` | `Prettify` | Flattens an intersection type into a single readable object type.  IDE tooltips for intersections li |
| `@helpers4/type` | `RequiredKeys` | Extracts the required (non-optional) keys of an object type `T`. |
| `@helpers4/type` | `UnionToIntersection` | Converts a union type to an intersection type: `A | B | C` → `A & B & C`.  Uses conditional-type dis |
| `@helpers4/type` | `ValueOf` | Produces a union of all value types of an object type `T`. |
| `@helpers4/url` | `cleanPath` | Clean an URL by removing duplicate slashes. The protocol part of the URL is not modified. |
| `@helpers4/url` | `extractPureURI` | Extracts the pure URI from a URL by removing query parameters and fragments. |
| `@helpers4/url` | `onlyPath` | Extract only the path from an URI with optional query and fragments.  For example, all these paramet |
| `@helpers4/url` | `parsePackageRepository` | Parse the `repository` field from `package.json` into a structured object.  Supports all npm-specifi |
| `@helpers4/url` | `relativeURLToAbsolute` | Converts a relative URL to an absolute URL using the current document base URI. |
| `@helpers4/url` | `withLeadingSlash` | Adds a leading slash `/` to the given URL if it is not already present.  This function is useful for |
| `@helpers4/url` | `withoutLeadingSlash` | Removes the leading slash `/` from the given URL if it is present.  This function is useful for ensu |
| `@helpers4/url` | `withoutTrailingSlash` | Removes the trailing slash `/` from the given URL if it is present.  This function is useful for ens |
| `@helpers4/url` | `withTrailingSlash` | Adds a trailing slash `/` to the given URL if it is not already present.  This function is useful fo |
| `@helpers4/version` | `compare` | Compares two semantic version strings according to SemVer 2.0.0 specification  Supports: - Core vers |
| `@helpers4/version` | `increment` | Increments a semantic version |
| `@helpers4/version` | `incrementPrerelease` | Increments the prerelease portion of a semantic version — the semantics `npm version prerelease --pr |
| `@helpers4/version` | `isPrerelease` | Returns `true` when the version string has a prerelease suffix (i.e. contains a `-` after the core ` |
| `@helpers4/version` | `parse` | Parses a semantic version string into its components according to SemVer 2.0.0 specification  Suppor |
| `@helpers4/version` | `satisfiesRange` | Checks if a version satisfies a range (simple implementation) |
| `@helpers4/version` | `stringify` | Reconstruct a semantic version string from a ParsedVersion object.  This is the inverse of parse: `s |
| `@helpers4/version` | `stripV` | Strip the leading "v" from a version string if it exists. |

---

## API Reference by Category

## array

Package: `@helpers4/array`

### `cartesianProduct`

Computes the Cartesian product of the provided arrays.

Returns all possible tuples formed by picking one element from each input array,
in lexicographic order relative to the input order.

```typescript
import { cartesianProduct } from '@helpers4/array';

cartesianProduct<T extends readonly readonly unknown[][]>(arrays: T): mapped[]
```

**Parameters:**

- `arrays: T` — Two or more arrays to combine.

**Returns:** `mapped[]` — An array of tuples, each containing one element from each input array.

**Examples:**

*Combine two arrays*

Returns all ordered pairs from two arrays.

```typescript
cartesianProduct([1, 2], ['a', 'b'])
// => [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
```

*Generate product combinations*

Useful for generating all size/color variant combinations.

```typescript
cartesianProduct(['S', 'M', 'L'], ['red', 'blue'])
// => [['S','red'],['S','blue'],['M','red'],['M','blue'],['L','red'],['L','blue']]
```

*Empty input returns empty array*

If any input array is empty, the result is an empty array.

```typescript
cartesianProduct([1, 2], []) // => []
```

---

### `chunk`

Chunks an array into smaller arrays of specified size.
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { chunk } from '@helpers4/array';

chunk<T>(array: readonly T[] | null | undefined, size: number): T[][]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to chunk
- `size: number` — The size of each chunk

**Returns:** `T[][]` — Array of chunks

**Examples:**

*Split an array into pairs*

Chunks an array of 5 elements into groups of 2, with the last chunk containing the remainder.

```typescript
chunk([1, 2, 3, 4, 5], 2)
// => [[1, 2], [3, 4], [5]]
```

*Handle exact divisions*

When the array length is evenly divisible by the chunk size, all chunks are equal.

```typescript
chunk([1, 2, 3, 4], 2)
// => [[1, 2], [3, 4]]
```

*Return empty array for invalid size*

A size of 0 or negative returns an empty array.

```typescript
chunk([1, 2, 3], 0)
// => []
```

---

### `combineSortFns`

Chains multiple sort functions into a single comparator: the first function
decides the order unless it reports a tie (`0`), in which case the next
function is tried, and so on.

Lets you compose comparators of different kinds — e.g. a boolean-property
comparator from createSortByBooleanFn followed by a string-property
comparator from `createSortByStringFn` — which a single multi-key call
cannot express, since that coerces every key to the same comparison type.

```typescript
import { combineSortFns } from '@helpers4/array';

combineSortFns<T>(fns: readonly SortFn<T>[]): SortFn<T>
```

**Parameters:**

- `fns: readonly SortFn<T>[]` — Sort functions to try, in priority order. An empty list produces
  a stable no-op comparator (all elements compare equal).

**Returns:** `SortFn<T>` — A single sort function equivalent to applying `fns` in order

**Examples:**

*Default items first, then alphabetical*

Chain a boolean comparator with a string comparator to break ties.

```typescript
const items = [
  { isDefault: false, label: 'Bob' },
  { isDefault: true, label: 'Zoe' },
  { isDefault: false, label: 'Alice' },
];
items.sort(combineSortFns(
  createSortByBooleanFn('isDefault'),
  createSortByStringFn('label'),
))
// => [Zoe (default), Alice, Bob]
```

*Falls through when the first comparator ties*

A `0` result from the first function moves on to the next one.

```typescript
const rows = [{ a: 1, b: 2 }, { a: 1, b: 1 }];
rows.sort(combineSortFns<{ a: number; b: number }>(
  (x, y) => x.a - y.a,
  (x, y) => x.b - y.b,
))
// => [{ a: 1, b: 1 }, { a: 1, b: 2 }]
```

---

### `compact`

Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array.
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { compact } from '@helpers4/array';

compact<T>(array: readonly Falsy | T[] | null | undefined): Exclude<T, Falsy>[]
```

**Parameters:**

- `array: readonly Falsy | T[] | null | undefined` — The array to compact

**Returns:** `Exclude<T, Falsy>[]` — A new array with only truthy values

**Examples:**

*Remove falsy values*

Removes all falsy values (false, null, undefined, 0, "", NaN) from an array.

```typescript
compact([0, 1, false, 2, '', 3, null, undefined, NaN])
// => [1, 2, 3]
```

*Filter nullable strings*

Useful to clean up arrays with null/undefined gaps.

```typescript
compact(['hello', null, 'world', undefined, ''])
// => ['hello', 'world']
```

---

### `countBy`

Groups the elements of an array by the key returned by `keyFn` and returns a
record mapping each key to the number of matching elements.
`null` and `undefined` are treated as empty arrays and return `{}`.
Items whose computed key is a prototype-polluting string (`__proto__`,
`constructor`, `prototype`) are silently skipped.

```typescript
import { countBy } from '@helpers4/array';

countBy<T, K extends PropertyKey>(array: readonly T[] | null | undefined, keyFn: function): Partial<Record<Exclude<K, UnsafeKey>, number>>
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to count.
- `keyFn: function` — A function that returns the grouping key for each element.

**Returns:** `Partial<Record<Exclude<K, UnsafeKey>, number>>` — A `Partial<Record<K, number>>` where each key maps to its element count.
  Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are never present.

**Examples:**

*Count by parity*

Groups items by the string key returned by the callback and counts occurrences.

```typescript
countBy([1, 2, 3, 4, 5], n => n % 2 === 0 ? 'even' : 'odd')
// => { odd: 3, even: 2 }
```

*Count commit types*

Use any string transform as the grouping key.

```typescript
const commits = ['feat: add x', 'fix: bug', 'feat: add y'];
countBy(commits, msg => msg.split(':')[0])
// => { feat: 2, fix: 1 }
```

---

### `createSortByBooleanFn`

Creates a sort function for objects by a boolean property.

Values are coerced with `Boolean()` before comparing, so `null`, `undefined`,
`0`, and `''` behave as `false`, and any other truthy value behaves as `true`.

```typescript
import { createSortByBooleanFn } from '@helpers4/array';

createSortByBooleanFn<T extends Record<string, unknown>>(property: keyof T, trueFirst: boolean): SortFn<T>
```

**Parameters:**

- `property: keyof T` — The property to sort by.
- `trueFirst: boolean` (default: `true`) — Whether `true` values sort before `false` values (default: `true`).

**Returns:** `SortFn<T>` — Sort function

**Examples:**

*Sort objects with true values first*

Default items float to the top of the list.

```typescript
const items = [{ isDefault: false }, { isDefault: true }, { isDefault: false }];
items.sort(createSortByBooleanFn('isDefault'))
// => [{ isDefault: true }, { isDefault: false }, { isDefault: false }]
```

*Sort with false values first*

Pass trueFirst = false to invert the priority.

```typescript
const items = [{ archived: true }, { archived: false }];
items.sort(createSortByBooleanFn('archived', false))
// => [{ archived: false }, { archived: true }]
```

---

### `createSortByDateFn`

Creates a sort function for objects by date property.

```typescript
import { createSortByDateFn } from '@helpers4/array';

createSortByDateFn<T extends Record<string, unknown>>(property?: keyof T): SortFn<T>
```

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to `'date'`).
  Accepted value types: `Date` (including cross-realm instances), `string`, or
  `number` (Unix milliseconds). Any object with a `getTime(): number` method is
  also accepted (duck-typed, so cross-realm `Date` objects work correctly).
  `null`, `undefined`, and unparseable strings produce `NaN` and sort last,
  distinct from a genuine Unix-epoch date (`new Date(0)`).

**Returns:** `SortFn<T>` — Sort function

**Examples:**

*createSortByDateFn*

```typescript
```ts
const events = [
  { date: new Date('2023-01-01') },
  { date: new Date('2021-06-15') },
];
events.sort(createSortByDateFn('date'))
// => sorted oldest first
```
```

---

### `createSortByNaturalFn`

Creates a sort function for objects by one or more string properties using
natural ordering. Numbers embedded in values are compared numerically:
"W2" < "W11" < "W20". When multiple properties are given, ties on the
first key are broken by the second key, then the third, and so on.

```typescript
import { createSortByNaturalFn } from '@helpers4/array';

createSortByNaturalFn<T extends Record<string, unknown>>(property?: keyof T | readonly keyof T[], caseInsensitive: boolean): SortFn<T>
```

**Parameters:**

- `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by.
  Defaults to trying 'value', 'label', 'title', 'description' in that order.
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case **and diacritics** (default: false).
  Uses `Intl.Collator { sensitivity: 'base' }`, which treats é, E, and e as equal.
  This differs from `createSortByStringFn(key, true)`, which only folds case and
  still distinguishes accented characters (é ≠ e).

**Returns:** `SortFn<T>` — Sort function

**Examples:**

*createSortByNaturalFn*

```typescript
```ts
const items = [{ label: 'W11' }, { label: 'W2' }, { label: 'W20' }];
items.sort(createSortByNaturalFn('label'))
// => [{ label: 'W2' }, { label: 'W11' }, { label: 'W20' }]
```
```

---

### `createSortByNumberFn`

Creates a sort function for objects by number property.

```typescript
import { createSortByNumberFn } from '@helpers4/array';

createSortByNumberFn<T extends Record<string, unknown>>(property?: keyof T): SortFn<T>
```

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to `'value'`). Always pass an
  explicit key when T does not have a `'value'` property — omitting it on such types
  produces a no-op comparator (all elements compare equal).
  `null` and `undefined` sort last (treated as `+Infinity`). Non-numeric values
  (including booleans — `Number(true) === 1`, `Number(false) === 0`) and `NaN` also
  sort last.

**Returns:** `SortFn<T>` — Sort function

**Examples:**

*createSortByNumberFn*

```typescript
```ts
const items = [{ count: 3 }, { count: 1 }, { count: 2 }];
items.sort(createSortByNumberFn('count'))
// => [{ count: 1 }, { count: 2 }, { count: 3 }]
```
```

---

### `createSortByStringFn`

Creates a sort function for objects by one or more string properties.
When multiple properties are given the array is sorted by the first key;
ties are broken by the second key, then the third, and so on.

Property values are coerced to strings via `String()` before comparison:
numbers sort as `'0'`, `'1'`, `'42'`, etc. (lexicographic, not numeric);
use `createSortByNumberFn` for numeric properties.

```typescript
import { createSortByStringFn } from '@helpers4/array';

createSortByStringFn<T extends Record<string, unknown>>(property?: keyof T | readonly keyof T[], caseInsensitive: boolean): SortFn<T>
```

**Parameters:**

- `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by.
  Defaults to trying 'value', 'label', 'title', 'description' in that order.
  Pass `undefined` explicitly to use auto-detect; an empty array `[]` produces a
  stable no-op comparator (does **not** fall back to auto-detect).
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case (default: false).
  Uses `Intl.Collator { sensitivity: 'accent' }`, which folds case but still
  distinguishes accented characters (é ≠ e). This differs from
  `createSortByNaturalFn(key, true)`, which also collapses diacritics.

**Returns:** `SortFn<T>` — Sort function

**Examples:**

*Sort objects by string property*

Use createSortByStringFn to sort objects by a specific string property.

```typescript
const items = [{ name: 'Charlie' }, { name: 'Alice' }, { name: 'Bob' }];
items.sort(createSortByStringFn('name'))
// => [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }]
```

*Sort objects by multiple keys*

Pass an array of keys; ties on the first key are broken by the next.

```typescript
const rows = [
  { dept: 'B', name: 'Alice' },
  { dept: 'A', name: 'Zoe' },
  { dept: 'B', name: 'Adam' },
  { dept: 'A', name: 'Anna' },
];
rows.sort(createSortByStringFn(['dept', 'name'] as const))
// => A:Anna, A:Zoe, B:Adam, B:Alice
```

---

### `difference`

Returns the difference between two arrays (items in first array but not in second).
`null` and `undefined` are treated as empty arrays:
`difference(null, b)` → `[]`; `difference(a, null)` → copy of `a`.

```typescript
import { difference } from '@helpers4/array';

difference<T>(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array1: readonly T[] | null | undefined` — First array
- `array2: readonly T[] | null | undefined` — Second array

**Returns:** `T[]` — Array with items from first array not present in second array

**Examples:**

*Get items only in the first array*

Returns elements present in the first array but not in the second.

```typescript
difference([1, 2, 3, 4], [2, 4])
// => [1, 3]
```

---

### `ensureArray`

Wraps a value in an array if it is not already one.
If the value is already an array, it is returned as-is.
If the value is null or undefined, returns an empty array.
When a depth is specified, the resulting array is flattened
to that depth (like `Array.prototype.flat(depth)`).

```typescript
import { ensureArray } from '@helpers4/array';

ensureArray<T>(value: T | readonly T[] | null | undefined): T[]
```

**Parameters:**

- `value: T | readonly T[] | null | undefined` — The value to ensure is an array

**Returns:** `T[]` — The value wrapped in an array, or the value itself if already an array

```typescript
import { ensureArray } from '@helpers4/array';

ensureArray<T>(value: T | readonly T[] | null | undefined, depth: number): unknown[]
```

**Parameters:**

- `value: T | readonly T[] | null | undefined` — The value to ensure is an array
- `depth: number` — Depth to flatten the resulting array

**Returns:** `unknown[]` — The flattened array (element types may differ from `T` due to flattening)

**Examples:**

*Wrap a single value*

Wraps a non-array value in an array.

```typescript
ensureArray('hello')
// => ['hello']
```

*Pass through an existing array*

Returns the array as-is if already an array.

```typescript
ensureArray([1, 2, 3])
// => [1, 2, 3]
```

*Handle null and undefined*

Returns an empty array for null or undefined values.

```typescript
ensureArray(null)
// => []
```

*Flatten nested arrays with depth*

Flattens the resulting array to a given depth, like Array.prototype.flat().

```typescript
ensureArray([[1, [2, 3]], [4]], 1)
// => [1, [2, 3], 4]
```

---

### `equalsDeep`

Recursive structural array equality.

Two arrays are equal when they have the same length and each pair of
elements at the same index is structurally equal:
- Arrays recurse with `equalsDeep`.
- Plain objects recurse key-by-key with structural comparison.
- `Date` instances are compared by their epoch value.
- All other values use strict equality (`===`), which means `NaN !== NaN`
  and special objects (Map, Set, RegExp, Promise, class instances\u2026) are
  compared by reference.

For positional one-level comparison use equalsShallow. For
order-independent comparison use equalsUnordered.

```typescript
import { equalsDeep } from '@helpers4/array';

equalsDeep<T>(arrA: readonly T[], arrB: readonly T[]): boolean
```

**Parameters:**

- `arrA: readonly T[]` — First array to compare
- `arrB: readonly T[]` — Second array to compare

**Returns:** `boolean` — `true` if arrays are deeply equal, `false` otherwise.

**Examples:**

*Compare nested arrays*

Deeply compares two arrays including nested structures.

```typescript
equalsDeep([[1, 2], [3]], [[1, 2], [3]])
// => true
```

*Detect nested differences*

Returns false when nested arrays differ.

```typescript
equalsDeep([[1, 2]], [[1, 3]])
// => false
```

---

### `equalsShallow`

Positional, one-level (shallow) array equality.

Two arrays are equal when they have the same length and each pair of
elements at the same index satisfies strict equality (`===`). No
recursion: nested arrays/objects are compared by reference.

For recursive structural comparison use equalsDeep. For
order-independent comparison use equalsUnordered.

```typescript
import { equalsShallow } from '@helpers4/array';

equalsShallow<T>(arrA: readonly T[], arrB: readonly T[]): boolean
```

**Parameters:**

- `arrA: readonly T[]` — First array to compare
- `arrB: readonly T[]` — Second array to compare

**Returns:** `boolean` — `true` if every element matches by `===` at the same index, `false` otherwise.

**Examples:**

*Compare identical arrays*

Uses JSON.stringify for a fast shallow comparison.

```typescript
equalsShallow([1, 2, 3], [1, 2, 3])
// => true
```

*Detect order differences*

Unlike equals, equalsShallow is order-sensitive.

```typescript
equalsShallow([1, 2], [2, 1])
// => false
```

---

### `equalsUnordered`

Order-independent (set-style) array equality.

Two arrays are considered equal when they have the same length and every
element of `arr1` has at least one structural match in `arr2` (and vice
versa via the length check). Nested arrays are compared recursively with
the same order-independent semantics. Nested plain objects are compared
with equalsShallow from `object/`. All other values use strict
equality (`===`).

Use this when the inputs represent unordered collections (sets, tags…).
For positional equality use equalsShallow or equalsDeep
from this category.

```typescript
import { equalsUnordered } from '@helpers4/array';

equalsUnordered<T>(arr1: readonly T[], arr2: readonly T[]): boolean
```

**Parameters:**

- `arr1: readonly T[]` — First array
- `arr2: readonly T[]` — Second array

**Returns:** `boolean` — `true` if both arrays contain the same items regardless of order, `false` otherwise.

**Examples:**

*Compare identical arrays regardless of order*

Returns true when both arrays contain the same elements, in any order.

```typescript
equalsUnordered([1, 2, 3], [3, 2, 1])
// => true
```

*Detect different arrays*

Returns false when arrays contain different elements.

```typescript
equalsUnordered([1, 2], [1, 3])
// => false
```

*Compare arrays of objects*

Supports shallow comparison of nested objects.

```typescript
equalsUnordered([{ a: 1 }], [{ a: 1 }])
// => true
```

---

### `filterAsync`

The async counterpart to `Array.prototype.filter`: runs `predicate` for every item and
resolves to the items whose predicate was truthy, in their original relative order.

`predicate` runs for every item up front (respecting `concurrency`) before any filtering
happens — unlike the sync `.filter()`, there's no short-circuiting, since every predicate
call may already be in flight by the time an earlier one resolves.

```typescript
import { filterAsync } from '@helpers4/array';

filterAsync<T>(array: readonly T[], predicate: function, concurrency?: number): Promise<T[]>
```

**Parameters:**

- `array: readonly T[]` — The array to filter
- `predicate: function` — Async (or sync) predicate called with `(item, index)`
- `concurrency?: number` — Maximum number of concurrent predicate calls. Defaults to unlimited.

**Returns:** `Promise<T[]>` — The items whose predicate resolved truthy, in original order

**Examples:**

*Keep only the files that pass an async check*

Every predicate call runs concurrently; matches keep their original order.

```typescript
await filterAsync(files, (file) => fileExists(file))
// => only the files that actually exist
```

---

### `forEachAsync`

The async counterpart to `Array.prototype.forEach`: runs `fn` for every item for its
side effects, discarding any return value. Prefer mapAsync when you need the
results — this only exists to signal that intent clearly, same as the sync
`forEach`/`map` pair.

```typescript
import { forEachAsync } from '@helpers4/array';

forEachAsync<T>(array: readonly T[], fn: function, concurrency?: number): Promise<void>
```

**Parameters:**

- `array: readonly T[]` — The array to iterate
- `fn: function` — Async (or sync) function called with `(item, index)`
- `concurrency?: number` — Maximum number of concurrent calls. Defaults to unlimited.

**Returns:** `Promise<void>` — A promise that resolves once every call has settled

**Examples:**

*Upload files with a concurrency cap*

Runs fn for its side effects only; at most `concurrency` calls at once.

```typescript
await forEachAsync(files, (file) => uploadFile(file), 3)
// uploads at most 3 files concurrently
```

---

### `intersection`

Compute the intersection of two arrays, meaning the elements that are present
in both arrays.
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { intersection } from '@helpers4/array';

intersection<T>(a: readonly T[] | null | undefined, b: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `a: readonly T[] | null | undefined` — First array
- `b: readonly T[] | null | undefined` — Second array

**Returns:** `T[]` — The intersection of the two arrays

**Examples:**

*Find common elements*

Returns elements present in both arrays.

```typescript
intersection([1, 2, 3], [2, 3, 4])
// => [2, 3]
```

---

### `intersects`

Simple helper that check if two lists shared at least an item in common.
`null` and `undefined` are treated as empty arrays and return `false`.

```typescript
import { intersects } from '@helpers4/array';

intersects<T>(a: readonly T[] | null | undefined, b: readonly T[] | null | undefined): boolean
```

**Parameters:**

- `a: readonly T[] | null | undefined` — One list
- `b: readonly T[] | null | undefined` — Another list

**Returns:** `boolean` — `true` if one item is in common, `false` otherwise.

**Examples:**

*Detect shared element*

Returns true when at least one element is shared between both arrays.

```typescript
intersects([1, 2, 3], [3, 4, 5])
// => true
```

*No common elements*

Returns false when no elements are shared.

```typescript
intersects([1, 2], [3, 4])
// => false
```

---

### `isEmpty`

Checks if an array is empty (has no elements).
`null` and `undefined` are treated as empty arrays and return `true`.

```typescript
import { isEmpty } from '@helpers4/array';

isEmpty(value: readonly unknown[] | null | undefined): value is readonly never[] | null | undefined
```

**Parameters:**

- `value: readonly unknown[] | null | undefined` — The array to check

**Returns:** `value is readonly never[] | null | undefined` — `true` if the array has no elements, or if `value` is `null`/`undefined`

**Examples:**

*Check if an array is empty*

Returns true only for arrays with no elements.

```typescript
isEmpty([])        // => true
isEmpty([1, 2, 3]) // => false
isEmpty([null])    // => false  (null is still an element)
```

*Guard before accessing first element*

Use isEmpty as an early-return guard for arrays, null, and undefined; the false branch is safely non-empty.

```typescript
function first<T>(arr: T[] | null | undefined): T | undefined {
  if (isEmpty(arr)) return undefined;
  return arr[0];
}
first([])        // => undefined
first(null)      // => undefined
first([1, 2])    // => 1
```

---

### `isNonEmpty`

Checks if an array is non-empty (has at least one element).
`null` and `undefined` are treated as empty arrays and return `false`.

```typescript
import { isNonEmpty } from '@helpers4/array';

isNonEmpty<T>(value: readonly T[] | null | undefined): value is readonly [T, T]
```

**Parameters:**

- `value: readonly T[] | null | undefined` — The array to check

**Returns:** `value is readonly [T, T]` — `true` if the array has at least one element; `false` for empty, `null`, or `undefined`

**Examples:**

*Check if an array has elements*

Returns true for arrays with at least one element, regardless of the element values.

```typescript
isNonEmpty([1, 2, 3]) // => true
isNonEmpty([null])    // => true  (null is still an element)
isNonEmpty([])        // => false
```

*Safe first-element access with type narrowing*

In the true branch, the type narrows to [T, ...T[]], making arr[0] always defined.

```typescript
function first<T>(arr: readonly T[]): T | undefined {
  if (isNonEmpty(arr)) return arr[0]; // arr[0] is T, not T | undefined
  return undefined;
}
first([1, 2]) // => 1
first([])     // => undefined
```

---

### `mapAsync`

The async counterpart to `Array.prototype.map`: applies `fn` to every item and resolves to
an array of the results, in input order — regardless of which call finishes first.

`Array.prototype.map(asyncFn)` alone doesn't do this: it returns an array of *unresolved*
promises (`Promise<R>[]`), not `Promise<R[]>` — you'd still need to wrap it in
`Promise.all(...)` yourself, and there'd be no way to cap concurrency. This does both.

With no `concurrency` given, every call starts immediately (like
`Promise.all(array.map(fn))`). Pass `concurrency` to cap how many run at once — e.g. to
avoid overwhelming an API. Rejects with the first error thrown by `fn`, same as
`Promise.all`; other already-started calls keep running in the background but their
outcome is ignored.

```typescript
import { mapAsync } from '@helpers4/array';

mapAsync<T, R>(array: readonly T[], fn: function, concurrency?: number): Promise<R[]>
```

**Parameters:**

- `array: readonly T[]` — The array to map over
- `fn: function` — Async (or sync) function called with `(item, index)`
- `concurrency?: number` — Maximum number of concurrent calls. Defaults to unlimited.

**Returns:** `Promise<R[]>` — The mapped results, in input order

**Examples:**

*Map over an array with an async function*

Every call starts immediately, results come back in input order.

```typescript
await mapAsync([1, 2, 3], async (n) => n * 2)
// => [2, 4, 6]
```

*Cap concurrency to avoid overwhelming an API*

At most `concurrency` calls run at once; the rest queue and wait.

```typescript
await mapAsync(urls, (url) => fetch(url).then(r => r.json()), 2)
// at most 2 concurrent fetch() calls
```

---

### `max`

Returns the maximum value in an array using a loop instead of spread,
avoiding the call stack overflow that occurs with `Math.max(...array)`
for very large arrays (> ~65 000 elements).
`null` and `undefined` are treated as empty arrays and return `undefined`.

```typescript
import { max } from '@helpers4/array';

max(array: readonly number[] | null | undefined): number | undefined
```

**Parameters:**

- `array: readonly number[] | null | undefined` — Array of numbers

**Returns:** `number | undefined` — Maximum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN`

**Examples:**

*Safe maximum for large arrays*

Unlike Math.max(...array), max() uses a loop and handles arrays of any size without stack overflow.

```typescript
max([3, 1, 4, 1, 5, 9])
// => 9

// Safe for 1 000 000+ elements (Math.max(...arr) would throw):
max(Array.from({ length: 1_000_000 }, (_, i) => i))
// => 999999
```

---

### `mean`

Calculates the arithmetic mean (average) of an array of numbers.
Returns `NaN` for an empty array.

Pairs with sum for aggregate operations.

```typescript
import { mean } from '@helpers4/array';

mean(array: readonly number[]): number
```

**Parameters:**

- `array: readonly number[]` — The array of numbers to average

**Returns:** `number` — The arithmetic mean, or `NaN` if the array is empty

**Examples:**

*Average a list of numbers*

Returns the arithmetic mean of the array; NaN for empty arrays.

```typescript
mean([1, 2, 3, 4])  // => 2.5
mean([10, 20, 30])  // => 20
mean([])            // => NaN
```

---

### `meanBy`

Calculates the arithmetic mean of numbers derived from each item of an array via an iteratee.
Returns `NaN` for an empty array, `null`, or `undefined` — matching sumBy, which
treats `null`/`undefined` as empty rather than throwing.

Pairs with sumBy for aggregate operations.

```typescript
import { meanBy } from '@helpers4/array';

meanBy<T>(array: readonly T[] | null | undefined, accessor: ByAccessor<T>): number
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array of items to average
- `accessor: ByAccessor<T>` — Function deriving a number from each item, or a property path

**Returns:** `number` — The arithmetic mean of the derived values, or `NaN` if the array is empty, `null`, or `undefined`

**Examples:**

*Average a property across objects*

Averages a derived numeric value instead of the items themselves.

```typescript
meanBy([{ price: 10 }, { price: 20 }], item => item.price)
// => 15
```

*Use a property path instead of a function*

A string (or key array) path is shorthand for a getter function.

```typescript
meanBy([{ price: 10 }, { price: 20 }], 'price')
// => 15
```

---

### `median`

Calculates the median (middle value) of an array of numbers. For an even-length array,
returns the average of the two middle values. Returns `NaN` for an empty array.
Does not mutate the input array.

The median is the 50th percentile — this delegates to it rather than duplicating
its sort/interpolation logic.

```typescript
import { median } from '@helpers4/array';

median(array: readonly number[]): number
```

**Parameters:**

- `array: readonly number[]` — The array of numbers

**Returns:** `number` — The median value, or `NaN` if the array is empty

**Examples:**

*Odd-length array*

Returns the middle value once sorted.

```typescript
median([3, 1, 2])
// => 2
```

*Even-length array*

Averages the two middle values.

```typescript
median([1, 2, 3, 4])
// => 2.5
```

---

### `min`

Returns the minimum value in an array using a loop instead of spread,
avoiding the call stack overflow that occurs with `Math.min(...array)`
for very large arrays (> ~65 000 elements).
`null` and `undefined` are treated as empty arrays and return `undefined`.

```typescript
import { min } from '@helpers4/array';

min(array: readonly number[] | null | undefined): number | undefined
```

**Parameters:**

- `array: readonly number[] | null | undefined` — Array of numbers

**Returns:** `number | undefined` — Minimum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN`

**Examples:**

*Safe minimum for large arrays*

Unlike Math.min(...array), min() uses a loop and handles arrays of any size without stack overflow.

```typescript
min([3, 1, 4, 1, 5, 9])
// => 1

// Safe for 1 000 000+ elements (Math.min(...arr) would throw):
min(Array.from({ length: 1_000_000 }, (_, i) => i))
// => 0
```

---

### `partition`

Splits an array into two groups based on a predicate function.
The first group contains elements for which the predicate returns true,
the second group contains the rest.
`null` and `undefined` are treated as empty arrays and return `[[], []]`.

```typescript
import { partition } from '@helpers4/array';

partition<T>(array: readonly T[] | null | undefined, predicate: function): [T[], T[]]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to partition
- `predicate: function` — Function that returns true for elements in the first group

**Returns:** `[T[], T[]]` — A tuple of two arrays: [matching, non-matching]

**Examples:**

*Split numbers by parity*

Splits an array into even and odd numbers using a predicate.

```typescript
partition([1, 2, 3, 4, 5], n => n % 2 === 0)
// => [[2, 4], [1, 3, 5]]
```

*Separate active and inactive users*

Partitions an array of objects based on a boolean property.

```typescript
const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
  { name: 'Charlie', active: true },
];
partition(users, u => u.active)
// => [[Alice, Charlie], [Bob]]
```

*Handle empty array*

Returns two empty arrays when the input is empty.

```typescript
partition([], () => true)
// => [[], []]
```

---

### `percentile`

Calculates the p-th percentile of an array of numbers using linear interpolation between
the closest ranks. Returns `NaN` for an empty array. Does not mutate the input array.

```typescript
import { percentile } from '@helpers4/array';

percentile(array: readonly number[], p: number): number
```

**Parameters:**

- `array: readonly number[]` — The array of numbers
- `p: number` — The percentile to compute, from `0` to `100`

**Returns:** `number` — The interpolated value at percentile `p`, or `NaN` if the array is empty

**Examples:**

*Median via the 50th percentile*

The 50th percentile is equivalent to the median.

```typescript
percentile([1, 2, 3, 4], 50)
// => 2.5
```

*Min and max via 0 and 100*

The 0th and 100th percentiles are the min and max.

```typescript
percentile([4, 1, 3, 2], 0)   // => 1
percentile([4, 1, 3, 2], 100) // => 4
```

---

### `range`

Generates an array of sequential numbers from start to end (exclusive).
If only one argument is provided, it generates numbers from 0 to that value.

```typescript
import { range } from '@helpers4/array';

range(startOrEnd: number, end?: number, step?: number): number[]
```

**Parameters:**

- `startOrEnd: number` — The start value (if end is provided) or end value (if end is omitted)
- `end?: number` — The end value (exclusive)
- `step?: number` — The increment between values (default: 1 or -1 depending on direction)

**Returns:** `number[]` — An array of sequential numbers

**Examples:**

*Generate a sequence from 0*

Creates an array of numbers from 0 to n-1 with a single argument.

```typescript
range(5)
// => [0, 1, 2, 3, 4]
```

*Generate a sequence with start and end*

Creates an array from start (inclusive) to end (exclusive).

```typescript
range(1, 5)
// => [1, 2, 3, 4]
```

*Generate a sequence with a custom step*

Creates an array with a specified increment between values.

```typescript
range(0, 10, 2)
// => [0, 2, 4, 6, 8]
```

*Generate a descending sequence*

Automatically produces a descending range when start > end.

```typescript
range(5, 0)
// => [5, 4, 3, 2, 1]
```

---

### `replaceOrAppend`

Returns a new array with the first item matching `predicate` replaced by
`item` — or `item` appended at the end if no match is found. The common
"upsert into a list" pattern.

```typescript
import { replaceOrAppend } from '@helpers4/array';

replaceOrAppend<T>(array: readonly T[] | null | undefined, item: T, predicate: function): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty.
- `item: T` — The replacement (or new) item
- `predicate: function` — Called with each existing item to find what to replace

**Returns:** `T[]` — A new array with `item` upserted

**Examples:**

*Upsert an item into a list*

Replaces the first match, or appends when nothing matches.

```typescript
const users = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }];
replaceOrAppend(users, { id: 1, name: 'A' }, (u) => u.id === 1)
// => [{ id: 1, name: 'A' }, { id: 2, name: 'b' }]
```

*Appends when no item matches*

A missing id is added at the end rather than silently dropped.

```typescript
replaceOrAppend([{ id: 1 }], { id: 2 }, (u) => u.id === 2)
// => [{ id: 1 }, { id: 2 }]
```

---

### `sample`

Picks one or more random elements from an array.
When called without a count, returns a single element or `undefined` if the array is empty.
When called with a count, returns an array of up to `count` random elements sampled without replacement.

```typescript
import { sample } from '@helpers4/array';

sample<T>(array: readonly T[] | null | undefined): T | undefined
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array to pick from

**Returns:** `T | undefined` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given

```typescript
import { sample } from '@helpers4/array';

sample<T>(array: readonly T[] | null | undefined, count: number): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array to pick from
- `count: number` — Optional number of elements to pick (without replacement)

**Returns:** `T[]` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given

**Examples:**

*Pick a single random element*

Without a count, returns one random element from the array.

```typescript
sample([1, 2, 3, 4, 5])
// => 3 (random element)
```

*Pick multiple random elements*

With a count, returns an array of random elements sampled without replacement.

```typescript
sample([1, 2, 3, 4, 5], 3)
// => [2, 5, 1] (3 random elements, without replacement)
```

*Empty array returns undefined*

Returns undefined when sampling from an empty array.

```typescript
sample([])
// => undefined
```

---

### `select`

Filters and transforms an array in a single pass.

Similar to `.filter(condition).map(mapper)` but iterates the array only once.
**Index semantics differ from `.filter().map()`:** the `index` passed to both
`condition` and `mapper` is the index in the **original** array, not the
post-filter position. Use index-agnostic callbacks when the two must behave
identically.

```typescript
import { select } from '@helpers4/array';

select<T, U>(array: readonly T[] | null | undefined, mapper: function, condition: function): U[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to process
- `mapper: function` — Transforms each item that passes the condition
- `condition: function` (default: `...`) — Determines which items to include; defaults to keeping all items

**Returns:** `U[]` — Mapped values for items that pass the condition

**Examples:**

*Filter and transform in one pass*

Keeps only items matching the condition and transforms them — equivalent to .filter().map() but with a single iteration.

```typescript
select([1, 2, 3, 4, 5], x => x * 2, x => x % 2 === 0)
// => [4, 8]
```

*Extract a field from matching objects*

Filter on a condition and pluck a specific property in a single readable call.

```typescript
const users = [
  { name: 'Alice', active: true },
  { name: 'Bob',   active: false },
  { name: 'Carol', active: true },
];
select(users, u => u.name, u => u.active)
// => ['Alice', 'Carol']
```

---

### `shuffle`

Randomly reorders elements of an array using the Fisher-Yates algorithm.
Returns a new array without mutating the original.
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { shuffle } from '@helpers4/array';

shuffle<T>(array: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to shuffle

**Returns:** `T[]` — A new array with the same elements in random order

**Examples:**

*Shuffle an array of numbers*

Returns a new array with the same elements in random order using the Fisher-Yates algorithm.

```typescript
shuffle([1, 2, 3, 4, 5])
// => [3, 1, 5, 2, 4] (random order)
```

*Original array is not mutated*

The original array remains unchanged.

```typescript
const original = ['a', 'b', 'c'];
const shuffled = shuffle(original);
// original is still ['a', 'b', 'c']
```

---

### `sortNumberAscFn`

Sort numbers in ascending order

**Examples:**

*Sort numbers ascending*

Use sortNumberAscFn as a comparator for Array.sort().

```typescript
[3, 1, 2].sort(sortNumberAscFn)
// => [1, 2, 3]
```

*Sort strings alphabetically*

Use sortStringAscFn for locale-aware string sorting.

```typescript
['banana', 'apple', 'cherry'].sort(sortStringAscFn)
// => ['apple', 'banana', 'cherry']
```

---

### `sortNumberDescFn`

Sort numbers in descending order

---

### `sortStringAscFn`

Sort strings in ascending order

---

### `sortStringAscInsensitiveFn`

Sort strings in ascending order (case insensitive)

---

### `sortStringDescFn`

Sort strings in descending order

---

### `sortStringNaturalAscFn`

Sort strings in ascending order using natural (human-friendly) ordering.
Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20".

**Examples:**

*Natural sort for strings with embedded numbers*

sortStringNaturalAscFn treats numeric parts as numbers: "W2" < "W11" < "W20".

```typescript
['W20', 'W2', 'W11', 'W01'].sort(sortStringNaturalAscFn)
// => ['W01', 'W2', 'W11', 'W20']
```

*Natural sort for object arrays*

createSortByNaturalFn sorts objects with embedded numbers in property values.

```typescript
const items = [{ code: 'W20' }, { code: 'W2' }, { code: 'W11' }, { code: 'W01' }];
items.sort(createSortByNaturalFn('code'))
// => W01, W2, W11, W20
```

---

### `sortStringNaturalAscInsensitiveFn`

Sort strings in ascending natural order, ignoring case **and diacritics**
(`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal).
Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20".

**Examples:**

*sortStringNaturalAscInsensitiveFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalAscInsensitiveFn) // => ['W2', 'W11', 'W20']
```
```

---

### `sortStringNaturalDescFn`

Sort strings in descending order using natural (human-friendly) ordering.
Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2".

**Examples:**

*sortStringNaturalDescFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalDescFn) // => ['W20', 'W11', 'W2']
```
```

---

### `sortStringNaturalDescInsensitiveFn`

Sort strings in descending natural order, ignoring case **and diacritics**
(`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal).
Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2".

**Examples:**

*sortStringNaturalDescInsensitiveFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalDescInsensitiveFn) // => ['W20', 'W11', 'W2']
```
```

---

### `sum`

Calculates the sum of an array of numbers.
`null` and `undefined` are treated as empty arrays and return `0`.

```typescript
import { sum } from '@helpers4/array';

sum(array: readonly number[] | null | undefined): number
```

**Parameters:**

- `array: readonly number[] | null | undefined` — The array of numbers to sum

**Returns:** `number` — The sum of all values, or `0` for an empty array, `null`, or `undefined`

**Examples:**

*Sum numbers*

Calculates the sum of an array of numbers.

```typescript
sum([1, 2, 3, 4])
// => 10
```

*Sum with negative numbers*

Handles negative numbers correctly.

```typescript
sum([10, -3, 5, -2])
// => 10
```

---

### `sumBy`

Calculates the sum of numbers derived from each item of an array via an iteratee.
`null` and `undefined` are treated as empty arrays and return `0`.

```typescript
import { sumBy } from '@helpers4/array';

sumBy<T>(array: readonly T[] | null | undefined, accessor: ByAccessor<T>): number
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array of items to sum
- `accessor: ByAccessor<T>` — Function deriving a number from each item, or a property path

**Returns:** `number` — The sum of all derived values, or `0` for an empty array, `null`, or `undefined`

**Examples:**

*Sum a property across objects*

Sums a derived numeric value instead of the items themselves.

```typescript
sumBy([{ price: 10 }, { price: 20 }], item => item.price)
// => 30
```

*Use a property path instead of a function*

A string (or key array) path is shorthand for a getter function.

```typescript
sumBy([{ price: 10 }, { price: 20 }], 'price')
// => 30
```

---

### `symmetricDifference`

Returns the symmetric difference between two arrays: items present in
exactly one of the two arrays (in either, but not both).
`null` and `undefined` are treated as empty arrays.

```typescript
import { symmetricDifference } from '@helpers4/array';

symmetricDifference<T>(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array1: readonly T[] | null | undefined` — First array
- `array2: readonly T[] | null | undefined` — Second array

**Returns:** `T[]` — Items unique to `array1` (in original order), followed by items unique to `array2`

**Examples:**

*Find items unique to either array*

Complements difference()/intersection() with the "in either, not both" set operation.

```typescript
symmetricDifference([1, 2, 3], [2, 3, 4])
// => [1, 4]
```

*Identical arrays have no symmetric difference*

Items shared by both arrays are excluded entirely.

```typescript
symmetricDifference(['a', 'b'], ['a', 'b'])
// => []
```

---

### `toggle`

Returns a new array with `item` removed if present, or appended if absent —
the common "toggle a selection" pattern.

By default, presence is checked with `SameValueZero` equality (like
`Array.prototype.includes`). Pass `key` to compare by a derived identity
instead — useful for toggling objects by id rather than by reference.

```typescript
import { toggle } from '@helpers4/array';

toggle<T>(array: readonly T[] | null | undefined, item: T, key?: function): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty.
- `item: T` — The item to add or remove
- `key?: function` — Optional function deriving the identity to compare by

**Returns:** `T[]` — A new array with `item` toggled

**Examples:**

*Toggle a selection*

Common pattern for multi-select UI state — add if absent, remove if present.

```typescript
toggle([1, 2, 3], 2)
// => [1, 3]
toggle([1, 3], 2)
// => [1, 3, 2]
```

*Toggle objects by a derived key*

Pass a key selector to compare by id instead of object reference.

```typescript
const selected = [{ id: 1 }, { id: 2 }];
toggle(selected, { id: 1 }, (x) => x.id)
// => [{ id: 2 }]
```

---

### `unique`

Removes duplicate values from an array.
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { unique } from '@helpers4/array';

unique<T>(array: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to remove duplicates from

**Returns:** `T[]` — New array with unique values only

**Examples:**

*Remove duplicates*

Returns a new array with duplicate values removed.

```typescript
unique([1, 2, 2, 3, 3, 3])
// => [1, 2, 3]
```

---

### `unzip`

Splits an array of tuples into separate arrays, one per position.

The inverse of zip.

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B>(pairs: readonly [A, B][] | null | undefined): [A[], B[]]
```

**Parameters:**

- `pairs: readonly [A, B][] | null | undefined` — Array of 2-tuples to unzip

**Returns:** `[A[], B[]]` — A tuple of two arrays: all first elements and all second elements

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B, C>(pairs: readonly [A, B, C][] | null | undefined): [A[], B[], C[]]
```

**Parameters:**

- `pairs: readonly [A, B, C][] | null | undefined` — Array of 2-tuples to unzip

**Returns:** `[A[], B[], C[]]` — A tuple of two arrays: all first elements and all second elements

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B, C, D>(pairs: readonly [A, B, C, D][] | null | undefined): [A[], B[], C[], D[]]
```

**Parameters:**

- `pairs: readonly [A, B, C, D][] | null | undefined` — Array of 2-tuples to unzip

**Returns:** `[A[], B[], C[], D[]]` — A tuple of two arrays: all first elements and all second elements

**Examples:**

*Split pairs into separate arrays*

The inverse of zip — separate each position into its own array.

```typescript
const pairs: [number, string][] = [[1, 'a'], [2, 'b'], [3, 'c']];
const [nums, letters] = unzip(pairs);

nums;    // => [1, 2, 3]
letters; // => ['a', 'b', 'c']
```

---

### `without`

Returns a new array with all occurrences of the given values removed.

Unlike `difference`, which operates on two arrays as set operands, `without`
uses a variadic API suited for removing known sentinel values inline.
Uses `SameValueZero` equality (same as `Array.prototype.includes`).
`null` and `undefined` are treated as empty arrays and return `[]`.

```typescript
import { without } from '@helpers4/array';

without<T>(array: readonly T[] | null | undefined, values: T[]): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array.
- `values: T[]` — One or more values to exclude from the result.

**Returns:** `T[]` — A new array without the specified values.

**Examples:**

*Remove a single value*

Returns a new array with all occurrences of the given value removed.

```typescript
without([1, 2, 3, 2, 4], 2)
// => [1, 3, 4]
```

*Remove multiple values*

All listed values are excluded from the result.

```typescript
without([1, 2, 3, 2, 4], 2, 3)
// => [1, 4]
```

---

### `zip`

Combines multiple arrays element-by-element into an array of tuples.
The result length equals the length of the shortest input array.

The inverse of unzip.

```typescript
import { zip } from '@helpers4/array';

zip<A, B>(a: readonly A[], b: readonly B[]): [A, B][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array

**Returns:** `[A, B][]` — Array of `[a, b]` pairs

```typescript
import { zip } from '@helpers4/array';

zip<A, B, C>(a: readonly A[], b: readonly B[], c: readonly C[]): [A, B, C][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array
- `c: readonly C[]`

**Returns:** `[A, B, C][]` — Array of `[a, b]` pairs

```typescript
import { zip } from '@helpers4/array';

zip<A, B, C, D>(a: readonly A[], b: readonly B[], c: readonly C[], d: readonly D[]): [A, B, C, D][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array
- `c: readonly C[]`
- `d: readonly D[]`

**Returns:** `[A, B, C, D][]` — Array of `[a, b]` pairs

**Examples:**

*Pair keys with values*

Combine two arrays element-by-element.

```typescript
zip(['a', 'b', 'c'], [1, 2, 3])
// => [['a', 1], ['b', 2], ['c', 3]]
```

*Truncates to the shorter array*

Stops at the end of the shorter array to avoid undefined entries.

```typescript
zip([1, 2, 3], ['x', 'y'])
// => [[1, 'x'], [2, 'y']]
```

---

## ci

Package: `@helpers4/ci`

### `buildStatusTable`

Builds a Markdown table body from a map of job names to CI/CD statuses.
Each row follows the format `| icon | **Job Name** | badge |`.

Intended to be embedded in a PR comment template:
```
| | Job | Status |
|:---:|-----|:------:|
${buildStatusTable(jobs)}
```

```typescript
import { buildStatusTable } from '@helpers4/ci';

buildStatusTable(jobs: Record<string, string>): string
```

**Parameters:**

- `jobs: Record<string, string>` — Record mapping job display names to their CI status

**Returns:** `string` — Newline-separated Markdown table rows (no header, no footer)

**Examples:**

*Build a PR comment status table*

Generates the body rows of a Markdown table for a PR validation summary.

```typescript
const rows = buildStatusTable({
  '🧾 Conventional Commits': 'success',
  '🐚 ShellCheck':           'failure',
  '🧪 Tests':                'skipped',
});

// Embed in a comment template:
// | | Job | Status |
// |:---:|-----|:------:|
// ${rows}
```

---

### `DEFAULT_PERCENTAGE_TIERS`

Default tiers, geared towards coverage/quality-gate style percentages.
Follows shields.io color conventions: brightgreen >= 100, green >= 90, yellow >= 80, orange >= 60.

---

### `percentageToTier`

Maps a numeric percentage to a tier (icon, color, label) using configurable thresholds.

Tiers are matched by their highest `min` that is `<= value`; a `value` below every tier's
`min` (e.g. a negative percentage, or custom tiers that don't cover down to 0) falls back to
the tier with the lowest `min` — there's always a match as long as `tiers` is non-empty.

```typescript
import { percentageToTier } from '@helpers4/ci';

percentageToTier(value: number, tiers: readonly PercentageTier[]): PercentageTier
```

**Parameters:**

- `value: number` — The percentage to classify (typically 0-100, but not clamped)
- `tiers: readonly PercentageTier[]` (default: `DEFAULT_PERCENTAGE_TIERS`) — Threshold tiers to match against, in any order (defaults to DEFAULT_PERCENTAGE_TIERS)

**Returns:** `PercentageTier` — The matched tier

**Examples:**

*Coverage report tier*

Maps a coverage percentage to a tier using the built-in default thresholds.

```typescript
percentageToTier(95.2)
// => { min: 90, icon: '🟢', color: 'green', label: 'excellent' }

percentageToTier(42)
// => { min: 0, icon: '🔴', color: 'red', label: 'poor' }
```

*Custom pass/fail tiers*

Supply your own tiers, e.g. a simple two-tier pass/fail gate.

```typescript
const tiers = [
  { min: 50, icon: '🟢', color: 'green', label: 'pass' },
  { min: 0, icon: '🔴', color: 'red', label: 'fail' },
];

percentageToTier(75, tiers)  // => { min: 50, icon: '🟢', color: 'green', label: 'pass' }
percentageToTier(25, tiers)  // => { min: 0, icon: '🔴', color: 'red', label: 'fail' }
```

---

### `statusToBadge`

Maps a CI/CD job status to an inline code badge string.

| Status | Badge |
|--------|-------|
| `success` | `` `passing` `` |
| `failure` | `` `failing` `` |
| `skipped` | `` `skipped` `` |
| *(other)* | `` `unknown` `` |

```typescript
import { statusToBadge } from '@helpers4/ci';

statusToBadge(status: CiStatus): string
```

**Parameters:**

- `status: CiStatus` — The CI/CD job status

**Returns:** `string` — A Markdown inline-code badge

**Examples:**

*Map CI status to a Markdown badge*

Returns a Markdown code-span badge string for the given CI status.

```typescript
statusToBadge('success')  // => '`passing`'
statusToBadge('failure')  // => '`failing`'
statusToBadge('skipped')  // => '`skipped`'
statusToBadge('pending')  // => '`unknown`'
```

---

### `statusToIcon`

Maps a CI/CD job status to an emoji icon.

| Status | Icon |
|--------|------|
| `success` | ✅ |
| `failure` | ❌ |
| `skipped` | ⏭️ |
| *(other)* | ⚠️ |

```typescript
import { statusToIcon } from '@helpers4/ci';

statusToIcon(status: CiStatus): string
```

**Parameters:**

- `status: CiStatus` — The CI/CD job status

**Returns:** `string` — An emoji representing the status

**Examples:**

*Map CI status to icon*

Returns an emoji icon matching the given CI status.

```typescript
statusToIcon('success')  // => '✅'
statusToIcon('failure')  // => '❌'
statusToIcon('skipped')  // => '⏭️'
statusToIcon('pending')  // => '⚠️'
```

---

## color

Package: `@helpers4/color`

### `argbToRgb`

Converts a 32-bit packed ARGB integer (as used by e.g. Chromium's
`Local State` profile `background_color` field) into a CSS `rgb()` string.
The alpha byte (top 8 bits) is read but discarded — the result is always opaque.

```typescript
import { argbToRgb } from '@helpers4/color';

argbToRgb(argb: number): string
```

**Parameters:**

- `argb: number` — A 32-bit integer where bits 24-31 are alpha, 16-23 are red,
  8-15 are green, and 0-7 are blue

**Returns:** `string` — A `rgb(r,g,b)` CSS color string

**Examples:**

*Convert a packed ARGB integer to a CSS color*

The top byte (alpha) is discarded — the result is always opaque.

```typescript
argbToRgb(0xffff0000)
// => 'rgb(255,0,0)'
```

*Alpha byte does not affect the result*

Two ARGB values differing only in the alpha byte produce the same rgb() string.

```typescript
argbToRgb(0x00ff0000) === argbToRgb(0x80ff0000)
// => true
```

---

### `hexToRgb`

Parses a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` — the
leading `#` is optional) into its RGB(A) channels.

```typescript
import { hexToRgb } from '@helpers4/color';

hexToRgb(hex: string): RgbColor | null
```

**Parameters:**

- `hex: string` — The hex color string to parse

**Returns:** `RgbColor | null` — The parsed color, or `null` if `hex` is not a valid hex color

**Examples:**

*Parse a hex color into RGB channels*

The leading # is optional; shorthand 3/4-digit forms are also supported.

```typescript
hexToRgb('#ff0000')
// => { r: 255, g: 0, b: 0, a: 1 }
```

*Returns null for an invalid color*

Lets you branch on parse failure instead of getting a garbage color.

```typescript
hexToRgb('not-a-color')
// => null
```

---

### `hslToRgb`

Converts an HSL(A) color into RGB(A).

```typescript
import { hslToRgb } from '@helpers4/color';

hslToRgb(color: HslColor): RgbColor
```

**Parameters:**

- `color: HslColor` — The color to convert. `h` is normalized modulo 360 (negative
  values wrap around), `s`/`l` are expected in 0-100, `a` defaults to 1
  (opaque) when omitted.

**Returns:** `RgbColor` — The equivalent RGB(A) color, with `r`/`g`/`b` rounded to 0-255

**Examples:**

*Convert an HSL color to RGB*

Useful after generating a hue programmatically (e.g. evenly spaced chart colors).

```typescript
hslToRgb({ h: 120, s: 100, l: 50 })
// => { r: 0, g: 255, b: 0, a: 1 }
```

*Hue wraps around 360 degrees*

Negative or out-of-range hues are normalized before conversion.

```typescript
hslToRgb({ h: -360, s: 100, l: 50 })
// => { r: 255, g: 0, b: 0, a: 1 }  (same as h: 0)
```

---

### `rgbToHex`

Converts an RGB(A) color into a hex color string.

`r`/`g`/`b` are clamped to 0-255 and rounded to the nearest integer before
formatting. The alpha channel is only appended (as `#rrggbbaa`) when it is
below 1 — fully opaque colors format as the plain 6-digit `#rrggbb`.

```typescript
import { rgbToHex } from '@helpers4/color';

rgbToHex(color: RgbColor): string
```

**Parameters:**

- `color: RgbColor` — The color to convert. `a` defaults to 1 (opaque) when omitted.

**Returns:** `string` — A lowercase hex color string

**Examples:**

*Format an opaque color as hex*

Alpha defaults to 1 (opaque) and is omitted from the output.

```typescript
rgbToHex({ r: 255, g: 0, b: 0 })
// => '#ff0000'
```

*Include alpha when the color is translucent*

A non-opaque color formats as an 8-digit #rrggbbaa string.

```typescript
rgbToHex({ r: 0, g: 255, b: 0, a: 0.5 })
// => '#00ff0080'
```

---

### `rgbToHsl`

Converts an RGB(A) color into HSL(A).

`h`/`s`/`l` are rounded to 1 decimal place to avoid floating-point noise.

```typescript
import { rgbToHsl } from '@helpers4/color';

rgbToHsl(color: RgbColor): HslColor
```

**Parameters:**

- `color: RgbColor` — The color to convert. `r`/`g`/`b` are expected in 0-255,
  `a` defaults to 1 (opaque) when omitted.

**Returns:** `HslColor` — The equivalent HSL(A) color: `h` in 0-360, `s`/`l` in 0-100

**Examples:**

*Convert an RGB color to HSL*

Useful for generating tints/shades by adjusting lightness.

```typescript
rgbToHsl({ r: 255, g: 0, b: 0 })
// => { h: 0, s: 100, l: 50, a: 1 }
```

*Grayscale colors have no saturation*

When r, g, and b are equal, the color is achromatic (s = 0).

```typescript
rgbToHsl({ r: 128, g: 128, b: 128 })
// => { h: 0, s: 0, l: 50.2, a: 1 }
```

---

## commit

Package: `@helpers4/commit`

### `analyzeCommits`

Analyses a list of commits to suggest a semantic version bump.

Each commit is parsed via `parseConventionalCommit`. The body is also
scanned for `BREAKING CHANGE:` / `BREAKING-CHANGE:` markers. The bump rule
is:

- any breaking change → `'major'`
- otherwise any `feat` → `'minor'`
- otherwise any `fix` → `'patch'`
- otherwise (non-empty list of non-conventional commits) → `'patch'`
- empty list → `'patch'` with reason "No commits to analyse"

```typescript
import { analyzeCommits } from '@helpers4/commit';

analyzeCommits(commits: readonly AnalyzableCommit[]): CommitAnalysis
```

**Parameters:**

- `commits: readonly AnalyzableCommit[]` — Iterable of commits to analyse. Only `subject` is required.

**Returns:** `CommitAnalysis` — Aggregated analysis with the suggested bump and reason.

**Examples:**

*Suggest a semver bump from a list of commits*

Walks through commits and suggests `major`, `minor`, or `patch` based on Conventional Commits.

```typescript
analyzeCommits([
  { subject: 'feat: add login' },
  { subject: 'fix: handle null' },
])
// => { suggestedBump: 'minor', hasFeatures: true, hasFixes: true, ... }
```

*Promote to major on breaking change*

A `!` marker or a `BREAKING CHANGE:` footer always promotes the suggestion to `major`.

```typescript
analyzeCommits([{ subject: 'feat!: drop v1 API' }]).suggestedBump
// => 'major'
```

---

### `buildConventionalCommitRegex`

Builds a regular expression matching the **subject line** of a Conventional
Commits message.

The returned regex exposes four capture groups:

1. type
2. scope (or `undefined` when absent)
3. breaking marker (`'!'` or `undefined`)
4. description

```typescript
import { buildConventionalCommitRegex } from '@helpers4/commit';

buildConventionalCommitRegex(options: ConventionalCommitOptions): RegExp
```

**Parameters:**

- `options: ConventionalCommitOptions` (default: `{}`) — Constrain accepted types/scopes and toggle scope requirement.

**Returns:** `RegExp` — Regex anchored on `^...$` matching the subject line only.

**Examples:**

*Match the default Conventional Commits format*

Returns a regex matching `type(scope)?!?: description` on the subject line.

```typescript
const regex = buildConventionalCommitRegex();
regex.test('feat(api): add endpoint') // => true
regex.test('not a commit') // => false
```

*Restrict accepted types and require a scope*

Constrain accepted types and force the scope segment to be present.

```typescript
const regex = buildConventionalCommitRegex({
  types: ['feat', 'fix'],
  requireScope: true,
});
regex.test('feat(api): x') // => true
regex.test('feat: missing scope') // => false
regex.test('chore(api): wrong type') // => false
```

---

### `isConventionalCommit`

Checks whether a commit message's subject line follows the Conventional
Commits format constrained by the given options.

Only the first line is inspected — body and footer are ignored.

```typescript
import { isConventionalCommit } from '@helpers4/commit';

isConventionalCommit(message: string, options?: ConventionalCommitOptions): boolean
```

**Parameters:**

- `message: string` — Full commit message or just its subject line.
- `options?: ConventionalCommitOptions` — Optional constraints (allowed types/scopes, scope requirement).

**Returns:** `boolean` — `true` when the subject line matches; `false` otherwise.

**Examples:**

*Validate a commit subject*

Returns `true` when the first line follows the Conventional Commits format.

```typescript
isConventionalCommit('feat(api): add endpoint') // => true
isConventionalCommit('hello world') // => false
```

*Restrict accepted types*

Reject any commit whose type is not in the supplied allowlist.

```typescript
isConventionalCommit('chore: x', { types: ['feat', 'fix'] }) // => false
isConventionalCommit('feat: x', { types: ['feat', 'fix'] }) // => true
```

---

### `parseConventionalCommit`

Parses a Conventional Commits message into a structured object.

The first line is matched against the regex produced by
`buildConventionalCommitRegex(options)`. The remaining content is split into
a `body` and an optional trailing `footer` block (lines matching
`Token: value` / `Token #value`, including `BREAKING CHANGE: ...`).

```typescript
import { parseConventionalCommit } from '@helpers4/commit';

parseConventionalCommit(message: string, options?: ConventionalCommitOptions): ParsedConventionalCommit | null
```

**Parameters:**

- `message: string` — Full commit message (subject + optional body/footer).
- `options?: ConventionalCommitOptions` — Optional constraints forwarded to the regex builder.

**Returns:** `ParsedConventionalCommit | null` — Parsed commit object, or `null` when the subject is not conventional.

**Examples:**

*Parse a Conventional Commits subject*

Extracts type, scope, breaking flag, and description.

```typescript
parseConventionalCommit('feat(api)!: add v2')
// => { type: 'feat', scope: 'api', breaking: true, description: 'add v2', body: '', footer: '' }
```

*Detect breaking changes from the footer*

A `BREAKING CHANGE:` footer flags the commit as breaking even without the `!` marker.

```typescript
parseConventionalCommit('feat: add option\n\nBREAKING CHANGE: drops old config').breaking
// => true
```

*Returns null on a non-conventional message*

Non-matching subjects return `null` rather than throwing.

```typescript
parseConventionalCommit('hello world') // => null
```

---

## date

Package: `@helpers4/date`

### `addDays`

Adds days to a date.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if the input is invalid.

```typescript
import { addDays } from '@helpers4/date';

addDays(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of days to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addDays*

```typescript
```ts
addDays('2025-01-19', 10)   // => Date(2025-01-29)
addDays('2025-01-19', -5)   // => Date(2025-01-14)
```
```

---

### `addMonths`

Adds months to a date.

Returns a **new** `Date` — the original is never mutated.
When the resulting month has fewer days, JavaScript clamps to the
next month (e.g. Jan 31 + 1 month → Mar 3). Use with caution.
Returns `null` if the input is invalid.

```typescript
import { addMonths } from '@helpers4/date';

addMonths(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of months to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addMonths*

```typescript
```ts
addMonths('2025-01-15', 1)    // => Date(2025-02-15)
addMonths('2025-01-31', 1)    // => Date(2025-03-03) — overflow
addMonths('2025-03-15', -1)   // => Date(2025-02-15)
```
```

---

### `addYears`

Adds years to a date.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if the input is invalid.

```typescript
import { addYears } from '@helpers4/date';

addYears(date: DateLike, amount: number): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `amount: number` — Number of years to add (negative to subtract)

**Returns:** `Date | null` — A new Date, or `null` if the input is invalid

**Examples:**

*addYears*

```typescript
```ts
addYears('2025-01-19', 1)    // => Date(2026-01-19)
addYears('2024-02-29', 1)    // => Date(2025-03-01) — leap year overflow
addYears('2025-06-15', -2)   // => Date(2023-06-15)
```
```

---

### `clampDate`

Clamps a date to a [min, max] range.

Returns a **new** `Date` — the original is never mutated.
Returns `null` if any of the inputs is invalid.

```typescript
import { clampDate } from '@helpers4/date';

clampDate(date: DateLike, min: DateLike, max: DateLike): Date | null
```

**Parameters:**

- `date: DateLike` — The date to clamp
- `min: DateLike` — The minimum allowed date
- `max: DateLike` — The maximum allowed date

**Returns:** `Date | null` — A new Date clamped to the range, or `null` if any input is invalid

**Examples:**

*clampDate*

```typescript
```ts
clampDate('2025-06-15', '2025-01-01', '2025-03-31')
// => Date(2025-03-31) — clamped to max

clampDate('2025-02-15', '2025-01-01', '2025-03-31')
// => Date(2025-02-15) — within range, unchanged
```
```

---

### `compare`

Comparison of two dates.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { compare } from '@helpers4/date';

compare(dateA: DateLike, dateB: DateLike, options: DateCompareOptions): boolean
```

**Parameters:**

- `dateA: DateLike` — First date to compare
- `dateB: DateLike` — Second date to compare
- `options: DateCompareOptions` (default: `{}`) — Comparison options

**Returns:** `boolean` — `true` if dates are identical according to the specified precision, `false` otherwise

**Examples:**

*Compare dates with millisecond precision*

By default, two identical Date objects are equal.

```typescript
const d = new Date('2025-01-19T12:00:00Z');
compare(d, new Date('2025-01-19T12:00:00Z'))
// => true
```

*Compare only by day*

Using day precision ignores the time part.

```typescript
compare(
  new Date('2025-01-19T08:00:00Z'),
  new Date('2025-01-19T23:59:59Z'),
  { precision: 'days' }
)
// => true
```

---

### `daysInMonth`

Returns the number of days in the given month of the given year.

Month is **1-based** (1 = January, 12 = December) to match human
convention and ISO 8601 (unlike `Date.getMonth()` which is 0-based).

Returns `NaN` if the month is out of range.

```typescript
import { daysInMonth } from '@helpers4/date';

daysInMonth(year: number, month: number): number
```

**Parameters:**

- `year: number` — A full year number (e.g. 2025)
- `month: number` — 1-based month number (1–12)

**Returns:** `number` — Number of days in that month, or `NaN` for invalid month

**Examples:**

*daysInMonth*

```typescript
```ts
daysInMonth(2025, 1)  // => 31 (January)
daysInMonth(2025, 2)  // => 28 (February, non-leap)
daysInMonth(2024, 2)  // => 29 (February, leap)
daysInMonth(2025, 4)  // => 30 (April)
```
```

---

### `difference`

Calculates the difference between two dates in the specified unit.

Accepts any DateLike input (Date, timestamp, or date string).

Unlike the removed `daysDifference`, the default `'days'` unit is **not** rounded —
it returns the exact fractional number of days. Wrap the result in `Math.round`
if you need a whole-number day count.

```typescript
import { difference } from '@helpers4/date';

difference(dateA: DateLike, dateB: DateLike, options: DateDifferenceOptions): number
```

**Parameters:**

- `dateA: DateLike` — First date
- `dateB: DateLike` — Second date
- `options: DateDifferenceOptions` (default: `{}`) — Difference options

**Returns:** `number` — The difference between the two dates, or `NaN` if either date is invalid

**Examples:**

*difference*

```typescript
```ts
difference('2025-01-01', '2025-01-10')
// => 9
difference('2025-01-01T00:00:00Z', '2025-01-01T12:00:00Z')
// => 0.5 (fractional — use Math.round(difference(a, b)) for a whole-day count)
difference('2025-01-01T00:00:00Z', '2025-01-01T02:30:00Z', { unit: 'hours' })
// => 2.5
difference('2025-01-10', '2025-01-01', { absolute: false })
// => -9
```
```

---

### `eachDay`

Returns an array of `Date` objects for each day from `start` to `end` (inclusive).

Both boundaries are included. If `start > end`, an empty array is returned.
Returns an empty array if either input is invalid.

```typescript
import { eachDay } from '@helpers4/date';

eachDay(start: DateLike, end: DateLike): Date[]
```

**Parameters:**

- `start: DateLike` — Start date (inclusive)
- `end: DateLike` — End date (inclusive)

**Returns:** `Date[]` — An array of Date objects, one per day

**Examples:**

*eachDay*

```typescript
```ts
eachDay('2025-01-01', '2025-01-03')
// => [Date(2025-01-01), Date(2025-01-02), Date(2025-01-03)]
```
```

---

### `eachMonth`

Returns an array of `Date` objects for the first day of each month
from `start` to `end` (inclusive).

Each returned Date is normalized to the 1st of the month at 00:00:00.000.
If `start > end`, an empty array is returned.
Returns an empty array if either input is invalid.

```typescript
import { eachMonth } from '@helpers4/date';

eachMonth(start: DateLike, end: DateLike): Date[]
```

**Parameters:**

- `start: DateLike` — Start date (inclusive — the month containing this date is included)
- `end: DateLike` — End date (inclusive — the month containing this date is included)

**Returns:** `Date[]` — An array of Date objects, one per month (each on the 1st)

**Examples:**

*eachMonth*

```typescript
```ts
eachMonth('2025-01-15', '2025-04-10')
// => [Date(2025-01-01), Date(2025-02-01), Date(2025-03-01), Date(2025-04-01)]
```
```

---

### `endOf`

Returns a new `Date` set to the **end** of the given unit.

- `'day'`   — 23:59:59.999
- `'month'` — last day of the month, 23:59:59.999
- `'year'`  — December 31st, 23:59:59.999

Returns `null` if the input is invalid.

```typescript
import { endOf } from '@helpers4/date';

endOf(date: DateLike, unit: DateTruncUnit): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `unit: DateTruncUnit` — The unit to round to

**Returns:** `Date | null` — A new Date at the end of the unit, or `null`

**Examples:**

*endOf*

```typescript
```ts
endOf('2025-06-15T14:30:00Z', 'day')    // => 2025-06-15T23:59:59.999Z
endOf('2025-06-15T14:30:00Z', 'month')  // => 2025-06-30T23:59:59.999Z
endOf('2025-06-15T14:30:00Z', 'year')   // => 2025-12-31T23:59:59.999Z
```
```

---

### `ensureDate`

Safely converts a date-like value to a valid `Date` object, or returns `null`.

Accepts `Date`, timestamps (seconds or milliseconds, auto-detected), date strings,
and objects with an `epochMilliseconds` property (e.g. `Temporal.Instant`,
`Temporal.ZonedDateTime`).
Returns `null` for `null`, `undefined`, empty strings, `0`, and any value that
produces an invalid `Date`.

This is the date equivalent of ensureArray — it normalizes flexible
input into a guaranteed type (or a safe fallback).

```typescript
import { ensureDate } from '@helpers4/date';

ensureDate(input: DateLike | null | undefined): Date | null
```

**Parameters:**

- `input: DateLike | null | undefined` — A date-like value to convert

**Returns:** `Date | null` — A valid `Date` object, or `null` if the input is invalid

**Examples:**

*ensureDate*

```typescript
```ts
ensureDate('2025-01-19T12:00:00Z') // => Date
ensureDate(1737290400)             // => Date (from Unix seconds)
ensureDate(1737290400000)          // => Date (from milliseconds)
ensureDate(new Date())             // => Date (same reference)
ensureDate(null)                   // => null
ensureDate('invalid')              // => null
```
```

---

### `formatDuration`

Formats a duration in milliseconds as a compact human-readable string.

Produces output like `"1h 23m 45s"`. Zero-valued leading units are
omitted (e.g. `"23m 45s"` instead of `"0h 23m 45s"`), but trailing
zeros are kept up to the minimum unit (`"1h 0m 0s"` when `minUnit`
is `'seconds'`).

Negative durations are prefixed with `"-"`.
A zero duration returns `"0s"` (or `"0m"` / `"0h"` depending on `minUnit`).

```typescript
import { formatDuration } from '@helpers4/date';

formatDuration(ms: number, options: FormatDurationOptions): string
```

**Parameters:**

- `ms: number` — Duration in milliseconds
- `options: FormatDurationOptions` (default: `{}`) — Optional configuration

**Returns:** `string` — A compact duration string

**Examples:**

*formatDuration*

```typescript
```ts
formatDuration(5025000)           // => "1h 23m 45s"
formatDuration(45000)             // => "45s"
formatDuration(3600000)           // => "1h 0m 0s"
formatDuration(5025000, { minUnit: 'minutes' }) // => "1h 23m"
formatDuration(5025000, { padded: true })       // => "01h 23m 45s"
formatDuration(-5025000)          // => "-1h 23m 45s"
```
```

---

### `formatInTimezone`

Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`.

Returns `null` if the date or timezone is invalid.

```typescript
import { formatInTimezone } from '@helpers4/date';

formatInTimezone(date: DateLike, tz: string, options: FormatInTimezoneOptions): string | null
```

**Parameters:**

- `date: DateLike` — The date to format
- `tz: string` — IANA timezone identifier (e.g. `'Asia/Tokyo'`)
- `options: FormatInTimezoneOptions` (default: `{}`) — Optional locale and format configuration

**Returns:** `string | null` — A formatted date string, or `null`

**Examples:**

*formatInTimezone*

```typescript
```ts
formatInTimezone('2025-01-19T12:00:00Z', 'Asia/Tokyo')
// => "1/19/2025, 9:00:00 PM" (en-US default)

formatInTimezone('2025-01-19T12:00:00Z', 'Europe/Paris', {
  locale: 'fr-FR',
  formatOptions: { dateStyle: 'long', timeStyle: 'short' },
})
// => "19 janvier 2025, 13:00"
```
```

---

### `fromMillis`

Creates a `Date` from a timestamp in **milliseconds**.

Use this when receiving a timestamp from a JS-native source
(e.g. `Date.now()`, `performance.timeOrigin`). No heuristic — the
input is always treated as milliseconds.

```typescript
import { fromMillis } from '@helpers4/date';

fromMillis(ms: number): Date | null
```

**Parameters:**

- `ms: number` — Milliseconds since the Unix epoch

**Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input

**Examples:**

*fromMillis*

```typescript
```ts
fromMillis(1737288000000) // => Date('2025-01-19T12:00:00Z')
fromMillis(0)             // => Date('1970-01-01T00:00:00Z')
fromMillis(NaN)           // => null
```
```

---

### `fromSeconds`

Creates a `Date` from a timestamp in **seconds**.

Use this when receiving a timestamp from a backend that sends seconds
(e.g. Java `Instant.getEpochSecond()`). No heuristic — the input is
always treated as seconds.

```typescript
import { fromSeconds } from '@helpers4/date';

fromSeconds(seconds: number): Date | null
```

**Parameters:**

- `seconds: number` — Seconds since the Unix epoch

**Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input

**Examples:**

*fromSeconds*

```typescript
```ts
fromSeconds(1737288000)  // => Date('2025-01-19T12:00:00Z')
fromSeconds(0)           // => Date('1970-01-01T00:00:00Z')
fromSeconds(NaN)         // => null
```
```

---

### `getTimezoneOffset`

Returns the UTC offset **in minutes** for the given IANA timezone
at a specific point in time.

A positive value means the timezone is **ahead** of UTC (e.g. `+60` for CET).
Returns `null` if the timezone is invalid or the date cannot be parsed.

The implementation uses `Intl.DateTimeFormat` to extract the local
representation in the target timezone, then computes the delta from UTC.

```typescript
import { getTimezoneOffset } from '@helpers4/date';

getTimezoneOffset(tz: string, date: DateLike): number | null
```

**Parameters:**

- `tz: string` — IANA timezone identifier (e.g. `'America/New_York'`)
- `date: DateLike` (default: `...`) — Reference date (defaults to now)

**Returns:** `number | null` — Offset in minutes, or `null` if inputs are invalid

**Examples:**

*getTimezoneOffset*

```typescript
```ts
getTimezoneOffset('America/New_York', '2025-01-19T12:00:00Z') // => -300 (EST)
getTimezoneOffset('Europe/Paris', '2025-07-19T12:00:00Z')     // => 120  (CEST)
```
```

---

### `isBusinessDay`

Checks whether a date falls on a business day (i.e. **not** a weekend day).

This is the logical inverse of isWeekend. By default, business days
are Monday through Friday. Pass a custom `weekendDays` to adapt to other
calendars.

> **Note:** This helper does **not** account for public holidays — those are
> country- and region-specific. Use it in combination with your own holiday
> list if needed.

Returns `false` if the input is invalid.

```typescript
import { isBusinessDay } from '@helpers4/date';

isBusinessDay(date: DateLike, weekendDays: readonly WeekDay[]): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)

**Returns:** `boolean` — `true` if the date is not a weekend day

**Examples:**

*isBusinessDay*

```typescript
```ts
isBusinessDay('2025-01-20') // => true  (Monday)
isBusinessDay('2025-01-18') // => false (Saturday)

// UAE weekend (Friday + Saturday)
const uaeWeekend = [WeekDays.Friday, WeekDays.Saturday] as const;
isBusinessDay('2025-01-19', uaeWeekend) // => true  (Sunday = workday)
isBusinessDay('2025-01-17', uaeWeekend) // => false (Friday = weekend)
```
```

---

### `isLeapYear`

Returns `true` if the given year is a leap year.

A year is a leap year when it is divisible by 4, **except** century
years which must also be divisible by 400.

```typescript
import { isLeapYear } from '@helpers4/date';

isLeapYear(year: number): boolean
```

**Parameters:**

- `year: number` — A full year number (e.g. 2024)

**Returns:** `boolean` — `true` if the year is a leap year

**Examples:**

*isLeapYear*

```typescript
```ts
isLeapYear(2024) // => true
isLeapYear(2025) // => false
isLeapYear(2000) // => true  (divisible by 400)
isLeapYear(1900) // => false (century, not divisible by 400)
```
```

---

### `isSameDay`

Checks if two dates are the same day.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameDay } from '@helpers4/date';

isSameDay(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same day, false otherwise (including when either date is invalid)

**Examples:**

*Same day, different times*

Returns true when both dates are on the same calendar day.

```typescript
isSameDay(new Date('2025-01-19T08:00:00'), new Date('2025-01-19T22:00:00'))
// => true
```

---

### `isSameMonth`

Checks if two dates are in the same month (and year).

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameMonth } from '@helpers4/date';

isSameMonth(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same month and year, false otherwise (including when either date is invalid)

**Examples:**

*isSameMonth*

```typescript
```ts
isSameMonth('2025-01-01', '2025-01-31') // => true
isSameMonth('2025-01-31', '2025-02-01') // => false
```
```

---

### `isSameYear`

Checks if two dates are in the same year.

Accepts any DateLike input (Date, timestamp, or date string).

```typescript
import { isSameYear } from '@helpers4/date';

isSameYear(date1: DateLike, date2: DateLike): boolean
```

**Parameters:**

- `date1: DateLike` — First date
- `date2: DateLike` — Second date

**Returns:** `boolean` — True if same year, false otherwise (including when either date is invalid)

**Examples:**

*isSameYear*

```typescript
```ts
isSameYear('2025-01-01', '2025-12-31') // => true
isSameYear('2024-12-31', '2025-01-01') // => false
```
```

---

### `isTimestampInSeconds`

Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style)

```typescript
import { isTimestampInSeconds } from '@helpers4/date';

isTimestampInSeconds(timestamp: number): boolean
```

**Parameters:**

- `timestamp: number` — The timestamp to check

**Returns:** `boolean` — True if timestamp appears to be in seconds

**Examples:**

*Detect a Unix timestamp in seconds*

Returns true for timestamps that are likely in seconds (Java/Unix style).

```typescript
isTimestampInSeconds(1737290400)
// => true
```

*Normalize a Unix timestamp to milliseconds*

Converts a timestamp in seconds to JavaScript milliseconds.

```typescript
normalizeTimestamp(1737290400)
// => 1737290400000
```

---

### `isValid`

Checks if a value is a valid Date instance (not `Invalid Date`).

Unlike `isDate` (in `type/`), this also verifies that the internal timestamp
is not `NaN`.

```typescript
import { isValid } from '@helpers4/date';

isValid(value: unknown): value is Date
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Date` — True if value is a Date instance with a valid time value

**Examples:**

*isValid*

```typescript
```ts
isValid(new Date())          // => true
isValid(new Date('invalid')) // => false
isValid('2023-01-01')        // => false (not a Date instance)
```
```

---

### `isValidDateString`

Checks whether a string can be parsed into a valid `Date`.

Uses the native `Date` constructor. Returns `false` for empty strings
and any string that produces an Invalid Date.

> **Caveat:** The native parser is lenient and implementation-dependent
> for non-ISO formats. For strict format validation, prefer a dedicated
> library or manual regex checks.

```typescript
import { isValidDateString } from '@helpers4/date';

isValidDateString(input: string): boolean
```

**Parameters:**

- `input: string` — The string to validate

**Returns:** `boolean` — `true` if `new Date(input)` produces a valid date

**Examples:**

*isValidDateString*

```typescript
```ts
isValidDateString('2025-01-19')            // => true
isValidDateString('2025-01-19T12:00:00Z')  // => true
isValidDateString('Jan 19, 2025')          // => true
isValidDateString('not a date')            // => false
isValidDateString('')                      // => false
```
```

---

### `isWeekend`

Checks whether a date falls on a weekend day.

By default, weekend days are **Saturday** and **Sunday** (Western
convention). Pass a custom `weekendDays` tuple to adapt to other
calendars (e.g. `[5, 6]` for Friday/Saturday in many Middle-Eastern
countries).

Returns `false` if the input is invalid.

```typescript
import { isWeekend } from '@helpers4/date';

isWeekend(date: DateLike, weekendDays: readonly WeekDay[]): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`)

**Returns:** `boolean` — `true` if the date's day-of-week is in `weekendDays`

**Examples:**

*isWeekend*

```typescript
```ts
isWeekend('2025-01-18') // => true  (Saturday)
isWeekend('2025-01-19') // => true  (Sunday)
isWeekend('2025-01-20') // => false (Monday)

// Middle-Eastern weekend (Friday + Saturday)
isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday]) // => true
isWeekend('2025-01-19', [WeekDays.Friday, WeekDays.Saturday]) // => false
```
```

---

### `isWithinRange`

Checks whether a date falls within a range (inclusive on both ends).

Returns `false` if any of the inputs is invalid.

```typescript
import { isWithinRange } from '@helpers4/date';

isWithinRange(date: DateLike, start: DateLike, end: DateLike): boolean
```

**Parameters:**

- `date: DateLike` — The date to check
- `start: DateLike` — Start of the range (inclusive)
- `end: DateLike` — End of the range (inclusive)

**Returns:** `boolean` — `true` if `start <= date <= end`

**Examples:**

*isWithinRange*

```typescript
```ts
isWithinRange('2025-06-15', '2025-01-01', '2025-12-31') // => true
isWithinRange('2024-06-15', '2025-01-01', '2025-12-31') // => false
```
```

---

### `listTimezones`

Returns the list of IANA timezone identifiers supported by the runtime.

Wraps `Intl.supportedValuesOf('timeZone')` which is available in
Node 18+, Chrome 93+, Firefox 93+, Safari 15.4+.

```typescript
import { listTimezones } from '@helpers4/date';

listTimezones(): string[]
```

**Returns:** `string[]` — An array of IANA timezone strings (e.g. `['Africa/Abidjan', …, 'US/Pacific']`)

**Examples:**

*listTimezones*

```typescript
```ts
listTimezones() // => ['Africa/Abidjan', 'Africa/Accra', …]
```
```

---

### `normalizeTimestamp`

Converts a timestamp to JavaScript milliseconds format

```typescript
import { normalizeTimestamp } from '@helpers4/date';

normalizeTimestamp(timestamp: number): number
```

**Parameters:**

- `timestamp: number` — The timestamp (in seconds or milliseconds)

**Returns:** `number` — Timestamp in milliseconds

---

### `overlaps`

Checks whether two date ranges overlap.

Two ranges overlap when `rangeA.start <= rangeB.end` AND
`rangeB.start <= rangeA.end` (inclusive on both ends).
Returns `false` if any date is invalid.

```typescript
import { overlaps } from '@helpers4/date';

overlaps(rangeA: DateRange, rangeB: DateRange): boolean
```

**Parameters:**

- `rangeA: DateRange` — First date range
- `rangeB: DateRange` — Second date range

**Returns:** `boolean` — `true` if the ranges share at least one point in time

**Examples:**

*overlaps*

```typescript
```ts
overlaps(
  { start: '2025-01-01', end: '2025-06-30' },
  { start: '2025-03-01', end: '2025-12-31' }
) // => true

overlaps(
  { start: '2025-01-01', end: '2025-02-28' },
  { start: '2025-03-01', end: '2025-12-31' }
) // => false
```
```

---

### `parseDuration`

Parses a compact duration string (as produced by formatDuration,
e.g. `"1h 23m 45s"`) back into milliseconds.

Accepts any combination/order of `h`/`m`/`s` segments, with or without
spaces between them (`"1h30m"` and `"1h 30m"` both work). A single leading
`-` negates the whole duration, matching `formatDuration`'s output.
Returns `null` when no valid segment is found.

```typescript
import { parseDuration } from '@helpers4/date';

parseDuration(str: string): number | null
```

**Parameters:**

- `str: string` — The duration string to parse

**Returns:** `number | null` — The duration in milliseconds, or `null` if unparseable

**Examples:**

*Parse a compact duration string into milliseconds*

The inverse of formatDuration() — accepts the same "1h 23m 45s" style output.

```typescript
parseDuration('1h 23m 45s')
// => 5025000
```

*Returns null for an unparseable string*

Lets you branch on parse failure instead of silently getting 0 or NaN.

```typescript
parseDuration('not a duration')
// => null
```

---

### `startOf`

Returns a new `Date` set to the **start** of the given unit.

- `'day'`   — 00:00:00.000
- `'month'` — 1st of the month, 00:00:00.000
- `'year'`  — January 1st, 00:00:00.000

Returns `null` if the input is invalid.

```typescript
import { startOf } from '@helpers4/date';

startOf(date: DateLike, unit: DateTruncUnit): Date | null
```

**Parameters:**

- `date: DateLike` — The base date
- `unit: DateTruncUnit` — The unit to truncate to

**Returns:** `Date | null` — A new Date at the start of the unit, or `null`

**Examples:**

*startOf*

```typescript
```ts
startOf('2025-06-15T14:30:00Z', 'day')    // => 2025-06-15T00:00:00.000Z
startOf('2025-06-15T14:30:00Z', 'month')  // => 2025-06-01T00:00:00.000Z
startOf('2025-06-15T14:30:00Z', 'year')   // => 2025-01-01T00:00:00.000Z
```
```

---

### `timeAgo`

Formats a date as a human-readable relative time string.

Uses `Intl.RelativeTimeFormat` under the hood, making the output
locale-aware (e.g. "il y a 3 jours" in French).

Returns `null` if the input date is invalid.

```typescript
import { timeAgo } from '@helpers4/date';

timeAgo(date: DateLike, options: TimeAgoOptions): string | null
```

**Parameters:**

- `date: DateLike` — The date to describe relative to `now`
- `options: TimeAgoOptions` (default: `{}`) — Optional configuration (reference date, locale, numeric style)

**Returns:** `string | null` — A locale-aware relative time string, or `null`

**Examples:**

*timeAgo*

```typescript
```ts
timeAgo('2025-01-17T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
// => "2 days ago"

timeAgo('2025-01-20T00:00:00Z', { now: '2025-01-19T00:00:00Z' })
// => "in 1 day"  (or "tomorrow" with numeric: 'auto')

timeAgo('2025-01-19T00:00:00Z', { now: '2025-01-19T00:00:05Z' })
// => "5 seconds ago"
```
```

---

### `toISO8601`

Converts a date to ISO 8601 format
Format: YYYY-MM-DDTHH:mm:ss.sssZ

```typescript
import { toISO8601 } from '@helpers4/date';

toISO8601(date: DateLike): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)

**Returns:** `string | null` — ISO 8601 formatted string or null if invalid date

**Examples:**

*Convert to ISO 8601*

Formats a date as an ISO 8601 string.

```typescript
toISO8601(new Date('2025-01-19T12:30:00Z'))
// => '2025-01-19T12:30:00.000Z'
```

*Convert to RFC 3339 (no ms)*

RFC 3339 format strips milliseconds by default.

```typescript
toRFC3339(new Date('2025-01-19T12:30:45.123Z'))
// => '2025-01-19T12:30:45Z'
```

*Convert to RFC 2822*

RFC 2822 is used in email and HTTP headers.

```typescript
toRFC2822(new Date('2025-01-19T12:30:00Z'))
// => 'Sun, 19 Jan 2025 12:30:00 +0000'
```

---

### `toMillis`

Converts a date to a timestamp in **milliseconds** (epoch millis).

Use this when you need a plain number from a `DateLike` value
(e.g. for `Date.now()` comparisons, localStorage, or JS-native APIs).

```typescript
import { toMillis } from '@helpers4/date';

toMillis(date: DateLike): number | null
```

**Parameters:**

- `date: DateLike` — The date to convert

**Returns:** `number | null` — Milliseconds since the Unix epoch, or `null` for invalid input

**Examples:**

*toMillis*

```typescript
```ts
toMillis('2025-01-19T12:00:00Z') // => 1737288000000
toMillis(null)                    // => null
```
```

---

### `toRFC2822`

Converts a date to RFC 2822 format
Format: Day, DD Mon YYYY HH:mm:ss +0000
Used in email headers (Date field) and HTTP headers

```typescript
import { toRFC2822 } from '@helpers4/date';

toRFC2822(date: DateLike): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)

**Returns:** `string | null` — RFC 2822 formatted string or null if invalid date

**Examples:**

*toRFC2822*

```typescript
```ts
toRFC2822(new Date('2025-01-19T12:30:00Z')) // 'Sun, 19 Jan 2025 12:30:00 +0000'
```
```

---

### `toRFC3339`

Converts a date to RFC 3339 format
Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm
RFC 3339 is a profile of ISO 8601, but without milliseconds by default

```typescript
import { toRFC3339 } from '@helpers4/date';

toRFC3339(date: DateLike, includeMilliseconds: boolean): string | null
```

**Parameters:**

- `date: DateLike` — Date to convert (Date object, timestamp, or date string)
- `includeMilliseconds: boolean` (default: `false`) — Whether to include milliseconds (default: false)

**Returns:** `string | null` — RFC 3339 formatted string or null if invalid date

**Examples:**

*toRFC3339*

```typescript
```ts
toRFC3339(new Date('2025-01-19T12:30:45.123Z')) // '2025-01-19T12:30:45Z'
toRFC3339(new Date('2025-01-19T12:30:45.123Z'), true) // '2025-01-19T12:30:45.123Z'
```
```

---

### `toSeconds`

Converts a date to a timestamp in **seconds** (epoch seconds).

Use this when sending a date to a backend API that expects seconds
(e.g. Java `Instant.getEpochSecond()`, Python `time.time()`).

```typescript
import { toSeconds } from '@helpers4/date';

toSeconds(date: DateLike): number | null
```

**Parameters:**

- `date: DateLike` — The date to convert

**Returns:** `number | null` — Seconds since the Unix epoch, or `null` for invalid input

**Examples:**

*toSeconds*

```typescript
```ts
toSeconds('2025-01-19T12:00:00Z') // => 1737288000
toSeconds(null)                    // => null
```
```

---

### `WeekDays`

Named day-of-week constants following the JavaScript `Date.getDay()`
convention. Use these instead of raw numbers for readability.

**Examples:**

*WeekDays*

```typescript
```ts
import { WeekDays } from '@helpers4/date';

isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday])
```
```

---

## function

Package: `@helpers4/function`

### `compose`

Composes functions right-to-left: `compose(f, g)(x)` is equivalent to `f(g(x))`.

The inverse of pipe, which applies functions left-to-right.

```typescript
import { compose } from '@helpers4/function';

compose<A, B>(fn1: function): function
```

**Parameters:**

- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C>(fn2: function, fn1: function): function
```

**Parameters:**

- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D>(fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D, E>(fn4: function, fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn4: function`
- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D, E, F>(fn5: function, fn4: function, fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn5: function`
- `fn4: function`
- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D, E, F, G>(fn6: function, fn5: function, fn4: function, fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn6: function`
- `fn5: function`
- `fn4: function`
- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D, E, F, G, H>(fn7: function, fn6: function, fn5: function, fn4: function, fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn7: function`
- `fn6: function`
- `fn5: function`
- `fn4: function`
- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

```typescript
import { compose } from '@helpers4/function';

compose<A, B, C, D, E, F, G, H, I>(fn8: function, fn7: function, fn6: function, fn5: function, fn4: function, fn3: function, fn2: function, fn1: function): function
```

**Parameters:**

- `fn8: function`
- `fn7: function`
- `fn6: function`
- `fn5: function`
- `fn4: function`
- `fn3: function`
- `fn2: function`
- `fn1: function`

**Returns:** `function` — A function that applies `fns` in reverse order

**Examples:**

*Compose functions right-to-left*

`compose(f, g)(x)` is equivalent to `f(g(x))`. The rightmost function is applied first.

```typescript
const process = compose(
  String,
  (x: number) => x * 2,
  (x: number) => x + 1
);
process(3); // => "8"
```

*Build a validator from small predicates*

Compose small predicate functions into a single validator.

```typescript
const validate = compose(
  (ok: boolean) => ok || (() => { throw new Error('invalid'); })(),
  (s: string) => s.length >= 3
);
validate('ab');  // throws
validate('abc'); // => true
```

---

### `curry`

Transforms a multi-argument function into a chain of single-argument functions
(Haskell-style currying). Supports up to 5 arguments.

The inverse operation of applying all arguments at once:
`curry(fn)(a)(b)` is equivalent to `fn(a, b)`.

```typescript
import { curry } from '@helpers4/function';

curry<A, R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to curry

**Returns:** `function` — A curried version of `fn`

```typescript
import { curry } from '@helpers4/function';

curry<A, B, R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to curry

**Returns:** `function` — A curried version of `fn`

```typescript
import { curry } from '@helpers4/function';

curry<A, B, C, R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to curry

**Returns:** `function` — A curried version of `fn`

```typescript
import { curry } from '@helpers4/function';

curry<A, B, C, D, R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to curry

**Returns:** `function` — A curried version of `fn`

```typescript
import { curry } from '@helpers4/function';

curry<A, B, C, D, E, R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to curry

**Returns:** `function` — A curried version of `fn`

**Examples:**

*Create reusable adder*

Curry a 2-argument function to build specialised versions.

```typescript
const add = curry((a: number, b: number) => a + b);
const add5 = add(5);

add5(3);  // => 8
add5(10); // => 15
```

*Pipeline-friendly 3-argument function*

Curry enables point-free style when composing pipelines.

```typescript
const clamp = curry((min: number, max: number, v: number) =>
  Math.min(Math.max(v, min), max)
);
const clamp0to100 = clamp(0)(100);

clamp0to100(42);  // => 42
clamp0to100(-5);  // => 0
clamp0to100(150); // => 100
```

---

### `debounce`

Creates a debounced function that delays invoking func until after delay milliseconds have elapsed since the last time the debounced function was invoked

```typescript
import { debounce } from '@helpers4/function';

debounce<A extends unknown[], R>(func: function, delay: number): function
```

**Parameters:**

- `func: function` — The function to debounce
- `delay: number` — The number of milliseconds to delay

**Returns:** `function` — The debounced function

**Examples:**

*Debounce a function*

The debounced function is only called once after the delay, even if invoked multiple times.

```typescript
const fn = debounce((x: number) => console.log(x), 100);
fn(1);
fn(2);
fn(3);
// Only logs 3 after 100ms
```

---

### `flip`

Creates a function that invokes `fn` with the first two arguments swapped.

Useful when adapting a function for use in higher-order pipelines where the
argument order is reversed (e.g. passing a binary callback to `reduce`).

```typescript
import { flip } from '@helpers4/function';

flip<A, B, Rest extends unknown[], R>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to wrap.

**Returns:** `function` — A new function with the first two parameters swapped.

**Examples:**

*Swap argument order*

Returns a new function where the first two arguments are swapped.

```typescript
const sub = (a: number, b: number) => a - b;
flip(sub)(3, 10); // => 7  (10 - 3)
```

*Adapt a divide function*

Useful for adapting binary callbacks in higher-order functions.

```typescript
const divide = (a: number, b: number) => a / b;
const divideInto = flip(divide);
divideInto(2, 100); // => 50
```

---

### `identity`

Returns the given value unchanged

Useful as a default transform, in function composition, or as a placeholder mapper.

```typescript
import { identity } from '@helpers4/function';

identity<T>(value: T): T
```

**Parameters:**

- `value: T` — The value to return

**Returns:** `T` — The same value

**Examples:**

*Return a primitive unchanged*

The value is returned as-is with its type preserved.

```typescript
identity(42);       // 42
identity('hello');  // 'hello'
identity(true);     // true
```

*Use as a default mapper*

Pass identity where a transform function is required but no transformation is needed.

```typescript
[1, 2, 3].map(identity); // [1, 2, 3]
```

---

### `memoize`

Returns a memoized version of the function that caches results.

Cache keys are derived via `JSON.stringify`; `undefined` arguments are
correctly distinguished from `null`. Arguments that are not JSON-serializable
(functions, symbols, class instances, circular references) produce a
`null`-equivalent key and are not supported.

```typescript
import { memoize } from '@helpers4/function';

memoize<A extends unknown[], R>(func: function, options?: MemoizeOptions): function
```

**Parameters:**

- `func: function` — The function to memoize
- `options?: MemoizeOptions` — Optional settings (e.g. `maxSize` to cap memory usage)

**Returns:** `function` — The memoized function

**Examples:**

*Cache function results*

The underlying function is only called once for the same arguments.

```typescript
let calls = 0;
const expensive = memoize((n: number) => { calls++; return n * 2; });
expensive(5); // => 10 (computed)
expensive(5); // => 10 (cached)
```

---

### `negate`

Creates a function that negates the result of `predicate`.

```typescript
import { negate } from '@helpers4/function';

negate<T extends unknown[]>(predicate: function): function
```

**Parameters:**

- `predicate: function` — A predicate function returning a boolean.

**Returns:** `function` — A new function that returns the logical negation of `predicate`.

**Examples:**

*Derive isOdd from isEven*

Returns a function that inverts the boolean result of the given predicate.

```typescript
const isEven = (n: number) => n % 2 === 0;
const isOdd = negate(isEven);
isOdd(3); // => true
isOdd(4); // => false
```

*Use as a filter predicate*

negate is ideal for inverting predicates passed to Array.filter.

```typescript
const isEmpty = (arr: unknown[]) => arr.length === 0;
[[], [1], [], [2, 3]].filter(negate(isEmpty))
// => [[1], [2, 3]]
```

---

### `noop`

A no-operation function that does nothing and returns `undefined`

Useful as a default callback, placeholder, or to explicitly ignore a value.

```typescript
import { noop } from '@helpers4/function';

noop(): void
```

**Returns:** `void` — Nothing (`undefined`)

**Examples:**

*Use as a default callback*

Replace an optional callback with noop to avoid null checks.

```typescript
const onComplete = options.callback ?? noop;
onComplete(); // does nothing
```

*Silence an event handler*

Pass noop wherever a function is required but no action is needed.

```typescript
element.addEventListener('click', noop);
```

---

### `once`

Creates a function that is restricted to be called only once.
Subsequent calls return the cached result of the first invocation.

The returned function exposes a `.reset()` method to clear the cache and
allow the original function to be called again.

```typescript
import { once } from '@helpers4/function';

once<A extends unknown[], R>(fn: function): OnceFn<A, R>
```

**Parameters:**

- `fn: function` — The function to wrap

**Returns:** `OnceFn<A, R>` — A function that invokes `fn` at most once, with a `.reset()` method

**Examples:**

*Run expensive setup only once*

The wrapped function executes only on the first call; all subsequent calls return the cached result.

```typescript
const init = once(() => ({ config: 'loaded' }));

const a = init(); // runs the function
const b = init(); // returns cached result
a === b; // => true

init.reset(); // clear cache
const c = init(); // runs the function again
a === c; // => false (new object)
```

*Guard against multiple event-listener registrations*

Ensure a side-effecting setup (e.g. addEventListener) runs at most once.

```typescript
const register = once((el: HTMLElement) => {
  el.addEventListener('click', handler);
});

register(button); // registers handler
register(button); // no-op — handler already registered
```

---

### `partial`

Partially applies arguments to a function, returning a new function that
accepts the remaining arguments.

```typescript
import { partial } from '@helpers4/function';

partial<A, R>(fn: function, a: A): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, R>(fn: function, a: A): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, C, R>(fn: function, a: A): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, C, R>(fn: function, a: A, b: B): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`
- `b: B`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, C, D, R>(fn: function, a: A): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, C, D, R>(fn: function, a: A, b: B): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`
- `b: B`

**Returns:** `function` — A function waiting for the remaining arguments

```typescript
import { partial } from '@helpers4/function';

partial<A, B, C, D, R>(fn: function, a: A, b: B, c: C): function
```

**Parameters:**

- `fn: function` — The function to partially apply
- `a: A`
- `b: B`
- `c: C`

**Returns:** `function` — A function waiting for the remaining arguments

**Examples:**

*Create a specialised multiplier*

Pre-fill the first argument to derive a specialised function.

```typescript
const multiply = (a: number, b: number) => a * b;
const double = partial(multiply, 2);
const triple = partial(multiply, 3);

double(5); // => 10
triple(5); // => 15
```

*Pre-fill multiple arguments*

Supply several arguments up front, leaving only the last one open.

```typescript
const format = (prefix: string, sep: string, value: string) =>
  `${prefix}${sep}${value}`;

const withLabel = partial(format, 'Status', ': ');
withLabel('passing'); // => 'Status: passing'
```

---

### `pipe`

Composes functions left-to-right: the output of each function is passed as
input to the next.

The inverse of compose, which applies functions right-to-left.

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B>(fn1: function): function
```

**Parameters:**

- `fn1: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C>(fn1: function, fn2: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D>(fn1: function, fn2: function, fn3: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D, E>(fn1: function, fn2: function, fn3: function, fn4: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`
- `fn4: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D, E, F>(fn1: function, fn2: function, fn3: function, fn4: function, fn5: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`
- `fn4: function`
- `fn5: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D, E, F, G>(fn1: function, fn2: function, fn3: function, fn4: function, fn5: function, fn6: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`
- `fn4: function`
- `fn5: function`
- `fn6: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D, E, F, G, H>(fn1: function, fn2: function, fn3: function, fn4: function, fn5: function, fn6: function, fn7: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`
- `fn4: function`
- `fn5: function`
- `fn6: function`
- `fn7: function`

**Returns:** `function` — A function that applies `fns` in order

```typescript
import { pipe } from '@helpers4/function';

pipe<A, B, C, D, E, F, G, H, I>(fn1: function, fn2: function, fn3: function, fn4: function, fn5: function, fn6: function, fn7: function, fn8: function): function
```

**Parameters:**

- `fn1: function`
- `fn2: function`
- `fn3: function`
- `fn4: function`
- `fn5: function`
- `fn6: function`
- `fn7: function`
- `fn8: function`

**Returns:** `function` — A function that applies `fns` in order

**Examples:**

*Transform a value through a pipeline*

Functions are applied left-to-right; the output of each becomes the input of the next.

```typescript
const process = pipe(
  (x: number) => x + 1,
  (x: number) => x * 2,
  String
);
process(3); // => "8"
```

*Sanitise a string*

Chain string transforms left-to-right with pipe.

```typescript
const sanitize = pipe(
  (s: string) => s.trim(),
  (s: string) => s.toLowerCase(),
  (s: string) => s.replace(/\s+/g, '-')
);
sanitize('  Hello World  '); // => "hello-world"
```

---

### `returnOrThrowError`

Return a value or throw an error if null or undefined.

```typescript
import { returnOrThrowError } from '@helpers4/function';

returnOrThrowError<T>(value: T | null | undefined, error: string): T
```

**Parameters:**

- `value: T | null | undefined` — A possible non-defined value.
- `error: string` — The error message to throw.

**Returns:** `T` — A defined value or an error.

**Examples:**

*Return a defined value*

Returns the value when it is defined and not null.

```typescript
returnOrThrowError('hello', 'Value is missing')
// => 'hello'
```

*Throw on null*

Throws an error when the value is null or undefined.

```typescript
returnOrThrowError(null, 'Value is missing')
// throws Error('Value is missing')
```

---

### `throttle`

Creates a throttled function that only invokes func at most once per every wait milliseconds

```typescript
import { throttle } from '@helpers4/function';

throttle<A extends unknown[], R>(func: function, wait: number): function
```

**Parameters:**

- `func: function` — The function to throttle
- `wait: number` — The number of milliseconds to throttle invocations to

**Returns:** `function` — The throttled function

**Examples:**

*Throttle rapid calls*

The throttled function is invoked at most once per wait period.

```typescript
const fn = throttle(() => console.log('tick'), 100);
fn(); // executes immediately
fn(); // ignored (within wait period)
```

---

### `unary`

Creates a function that calls `fn` with only its first argument, discarding
any others.

Prevents the classic footgun where a callback expecting extra positional
arguments is passed directly to `Array.prototype.map`:
`['1', '2', '3'].map(parseInt)` silently passes the array index as
`parseInt`'s radix argument, producing `[1, NaN, NaN]`.

```typescript
import { unary } from '@helpers4/function';

unary<F extends function>(fn: F): function
```

**Parameters:**

- `fn: F` — The function to restrict to one argument

**Returns:** `function` — A new function that calls `fn` with only its first argument

**Examples:**

*Fix the classic .map(parseInt) bug*

Array.prototype.map passes (value, index, array) — parseInt reads index as its radix argument.

```typescript
['1', '2', '3'].map(parseInt)
// => [1, NaN, NaN]  (bug: index used as radix)

['1', '2', '3'].map(unary(parseInt))
// => [1, 2, 3]
```

*Restrict any multi-argument function to one argument*

Useful whenever a callback slot passes more arguments than the function should see.

```typescript
const double = unary((n: number, factor?: number) => n * (factor ?? 2));
double(21)
// => 42
```

---

## guard

Package: `@helpers4/guard`

### `isArray`

Checks if a value is an array.

```typescript
import { isArray } from '@helpers4/guard';

isArray(value: unknown): value is unknown[]
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is unknown[]` — True if value is an array

**Examples:**

*isArray*

```typescript
```ts
isArray([1, 2, 3]) // => true
isArray('hello')   // => false
isArray({})        // => false
```
```

---

### `isArrayBuffer`

Checks if a value is an ArrayBuffer instance.

Useful for filtering or type-narrowing in a functional pipeline:
`values.filter(isArrayBuffer)`

```typescript
import { isArrayBuffer } from '@helpers4/guard';

isArrayBuffer(value: unknown): value is ArrayBuffer
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is ArrayBuffer` — True if value is an ArrayBuffer

**Examples:**

*Detect an ArrayBuffer*

Returns true only for ArrayBuffer instances, not TypedArray views.

```typescript
isArrayBuffer(new ArrayBuffer(8)) // => true
isArrayBuffer(new Uint8Array(8))  // => false
isArrayBuffer('hello')            // => false
```

*Filter ArrayBuffers from a mixed array*

Use as a predicate in .filter() to extract ArrayBuffer values.

```typescript
const values = [new ArrayBuffer(4), 'text', new ArrayBuffer(8), 42];
values.filter(isArrayBuffer)
// => [ArrayBuffer(4), ArrayBuffer(8)]
```

---

### `isArrayLike`

Checks if a value is array-like: has a non-negative integer `length` property.

Returns `true` for arrays, strings, `arguments` objects, `NodeList`, typed
arrays, and any object with a valid `length`. Functions are excluded even though
they have a `length` (arity), as they are not considered array-like in practice.

```typescript
import { isArrayLike } from '@helpers4/guard';

isArrayLike(value: unknown): value is ArrayLike<unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is ArrayLike<unknown>` — `true` if value is array-like

**Examples:**

*Detect array-like values*

Arrays, strings, and objects with a non-negative integer length are array-like.

```typescript
isArrayLike([1, 2, 3])       // => true
isArrayLike('hello')         // => true
isArrayLike({ length: 3 })   // => true
isArrayLike({ length: -1 })  // => false
isArrayLike(() => {})        // => false  (functions excluded)
isArrayLike(null)            // => false
```

*Convert an array-like value to an array*

Use as a guard before Array.from().

```typescript
function toArray(value: unknown): unknown[] {
  if (isArrayLike(value)) return Array.from(value);
  return [value];
}
toArray([1, 2])       // => [1, 2]
toArray('abc')        // => ['a', 'b', 'c']
toArray(42)           // => [42]
```

---

### `isAsyncFunction`

Checks if a value is an async function.

Returns `true` for any function declared with `async`.

```typescript
import { isAsyncFunction } from '@helpers4/guard';

isAsyncFunction(value: unknown): value is function
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is function` — True if value is an async function

**Examples:**

*isAsyncFunction*

```typescript
```ts
isAsyncFunction(async () => {})      // => true
isAsyncFunction(async function() {}) // => true
isAsyncFunction(() => {})            // => false
isAsyncFunction(42)                  // => false
```
```

---

### `isAsyncGenerator`

Checks if a value is an async generator object (the result of calling an `async function*`).

Distinct from isAsyncGeneratorFunction: this predicate targets the
*instance* produced by calling an async generator function, not the function itself.

```typescript
import { isAsyncGenerator } from '@helpers4/guard';

isAsyncGenerator(value: unknown): value is AsyncGenerator<unknown, unknown, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is AsyncGenerator<unknown, unknown, unknown>` — `true` if value is an AsyncGenerator instance

**Examples:**

*Detect an async generator instance*

Returns true only for the object produced by calling an async function*.

```typescript
async function* gen() { yield 1; }
isAsyncGenerator(gen())  // => true   (instance)
isAsyncGenerator(gen)    // => false  (function)
isAsyncGenerator([])     // => false
```

*Distinguish async from sync generators*

isAsyncGenerator is false for sync generator instances.

```typescript
function* sync() { yield 1; }
async function* async_() { yield 1; }
isAsyncGenerator(sync())   // => false
isAsyncGenerator(async_()) // => true
```

---

### `isAsyncGeneratorFunction`

Checks if a value is an async generator function (an `async function*` declaration or expression).

Distinct from isAsyncGenerator: this predicate targets the *function* itself,
not the async iterator it produces when called.

```typescript
import { isAsyncGeneratorFunction } from '@helpers4/guard';

isAsyncGeneratorFunction(value: unknown): value is AsyncGeneratorFunction
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is AsyncGeneratorFunction` — `true` if value is an AsyncGeneratorFunction

**Examples:**

*Detect an async generator function*

Returns true for async function* declarations and expressions.

```typescript
async function* gen() { yield 1; }
isAsyncGeneratorFunction(gen)         // => true
isAsyncGeneratorFunction(gen())       // => false  (instance)
isAsyncGeneratorFunction(async () => {}) // => false
```

*Distinguish async generator functions from sync generator functions*

isAsyncGeneratorFunction is false for sync function*.

```typescript
function* sync() { yield 1; }
async function* async_() { yield 1; }
isAsyncGeneratorFunction(sync)   // => false
isAsyncGeneratorFunction(async_) // => true
```

---

### `isAsyncIterable`

Checks if a value implements the async iterable protocol.

Returns `true` for any object that has a `[Symbol.asyncIterator]()` method,
including async generators. Note that regular iterables (arrays, strings, etc.)
are **not** async iterables.

```typescript
import { isAsyncIterable } from '@helpers4/guard';

isAsyncIterable(value: unknown): value is AsyncIterable<unknown, any, any>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is AsyncIterable<unknown, any, any>` — `true` if value is async iterable

**Examples:**

*Detect an async generator*

Async generators implement the async iterable protocol.

```typescript
async function* stream() { yield 1; yield 2; }
isAsyncIterable(stream())   // => true
isAsyncIterable([1, 2, 3])  // => false  (Iterable, not AsyncIterable)
isAsyncIterable('hello')    // => false
```

*Guard before for-await-of*

Use to type-narrow before consuming a value with for-await-of.

```typescript
async function consume(source: unknown) {
  if (isAsyncIterable(source)) {
    for await (const item of source) {
      console.log(item);
    }
  }
}
```

---

### `isBigInt`

Checks if a value is a bigint.

```typescript
import { isBigInt } from '@helpers4/guard';

isBigInt(value: unknown): value is bigint
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is bigint` — True if value is a bigint

**Examples:**

*isBigInt*

```typescript
```ts
isBigInt(42n)  // => true
isBigInt(42)   // => false
isBigInt('42') // => false
```
```

---

### `isBlob`

Checks if a value is a Blob instance.

Useful for filtering or type-narrowing in a functional pipeline:
`values.filter(isBlob)`

```typescript
import { isBlob } from '@helpers4/guard';

isBlob(value: unknown): value is Blob
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Blob` — True if value is a Blob

**Examples:**

*Detect a Blob*

Returns true only for Blob instances.

```typescript
isBlob(new Blob(['hello'])) // => true
isBlob(new Blob([], { type: 'application/json' })) // => true
isBlob('hello')             // => false
```

*Filter Blobs from a mixed array*

Use as a predicate in .filter() to extract Blob values.

```typescript
const values = [new Blob(['a']), 'text', new Blob(['b']), 42];
values.filter(isBlob)
// => [Blob, Blob]
```

---

### `isBoolean`

Checks if a value is a boolean.

```typescript
import { isBoolean } from '@helpers4/guard';

isBoolean(value: unknown): value is boolean
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is boolean` — True if value is a boolean

**Examples:**

*isBoolean*

```typescript
```ts
isBoolean(true)  // => true
isBoolean(false) // => true
isBoolean(1)     // => false
```
```

---

### `isBrowser`

Checks whether the code is currently running in a browser-like environment
(`window` and `window.document` both defined).

Note: some hybrid test environments (e.g. happy-dom, jsdom) define both `window` and Node's
`process` at the same time — this only reports on `window`'s presence, it does not imply
`isNode` is false. Use both together if you need to distinguish a real browser from a
DOM-emulating Node test environment.

```typescript
import { isBrowser } from '@helpers4/guard';

isBrowser(): boolean
```

**Returns:** `boolean` — `true` if `window` and `window.document` are both defined

**Examples:**

*Branch on the runtime environment*

Useful for isomorphic code that behaves differently in the browser vs. Node.js.

```typescript
if (isBrowser()) {
  localStorage.setItem('key', 'value')
} else {
  // Node.js / server-side fallback
}
```

---

### `isCssColor`

Checks whether a value is a syntactically-safe, plain CSS color: a hex color
(`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), a functional notation (`rgb()`,
`rgba()`, `hsl()`, `hsla()`), or a single-word named color (`red`,
`rebeccapurple`).

Intended to sanitize a color value before interpolating it into inline
`style`/`cssText` — it does not implement the full CSS color grammar or
validate named colors against the real keyword list, it only rejects
characters (`{`, `}`, `;`, ``) or shapes that could smuggle extra CSS
declarations into the surrounding rule.

```typescript
import { isCssColor } from '@helpers4/guard';

isCssColor(value: unknown): value is string
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is string` — `true` if value is a string that looks like a safe, plain CSS color

**Examples:**

*Validate a color before writing it to inline style*

Accepts hex, functional (rgb/rgba/hsl/hsla), and named colors.

```typescript
isCssColor('#ff0000')            // => true
isCssColor('rgba(0, 0, 0, 0.5)') // => true
isCssColor('rebeccapurple')      // => true
isCssColor(42)                   // => false
```

*Reject values that could inject extra CSS declarations*

Guards against untrusted data smuggling a second declaration into a style attribute.

```typescript
isCssColor('red; background: url(evil)') // => false
isCssColor('red}body{color:blue')        // => false
```

---

### `isDate`

Checks if a value is a Date instance.

Note: this only checks the type, not whether the Date is valid.
Use `date/isValid` to also validate that the Date is not `Invalid Date`.

```typescript
import { isDate } from '@helpers4/guard';

isDate(value: unknown): value is Date
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Date` — True if value is a Date instance

**Examples:**

*isDate*

```typescript
```ts
isDate(new Date())          // => true
isDate(new Date('invalid')) // => true (still a Date instance)
isDate('2023-01-01')       // => false
isDate(1609459200000)      // => false
```
```

---

### `isDefined`

Checks if a value is defined (not undefined nor null).
This is the inverse of isNullish.

Use as a type-safe filter callback to remove `null`/`undefined` from arrays.

```typescript
import { isDefined } from '@helpers4/guard';

isDefined<T>(value: Maybe<T>): value is T
```

**Parameters:**

- `value: Maybe<T>` — The value to check

**Returns:** `value is T` — True if value is not undefined nor null

**Examples:**

*isDefined*

```typescript
```ts
isDefined(42)        // => true
isDefined('')        // => true (empty string is defined)
isDefined(null)      // => false
isDefined(undefined) // => false
```
```

*isDefined*

```typescript
```ts
// Type-safe alternative to filter out null/undefined
const items: (string | null | undefined)[] = ['a', null, 'b', undefined];
const result = items.filter(isDefined);
// => ['a', 'b'] with type string[]
```
```

---

### `isError`

Checks if a value is an Error instance.

```typescript
import { isError } from '@helpers4/guard';

isError(value: unknown): value is Error
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Error` — True if value is an Error (or subclass like TypeError, RangeError, etc.)

**Examples:**

*isError*

```typescript
```ts
isError(new Error('oops'))     // => true
isError(new TypeError('bad'))  // => true
isError({ message: 'fake' })  // => false
```
```

---

### `isFalsy`

Checks if a value is falsy (`false`, `null`, `undefined`, `0`, `""`, `NaN`).

```typescript
import { isFalsy } from '@helpers4/guard';

isFalsy(value: unknown): value is Falsy
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Falsy` — True if the value is falsy

**Examples:**

*Check falsy values*

Returns true for all falsy values: false, null, undefined, 0, "", NaN.

```typescript
isFalsy(0)         // => true
isFalsy('')        // => true
isFalsy(null)      // => true
isFalsy('hello')   // => false
```

---

### `isFormData`

Checks if a value is a FormData instance.

Useful for filtering or type-narrowing in a functional pipeline:
`values.filter(isFormData)`

```typescript
import { isFormData } from '@helpers4/guard';

isFormData(value: unknown): value is FormData
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is FormData` — True if value is a FormData

**Examples:**

*Detect a FormData*

Returns true only for FormData instances.

```typescript
isFormData(new FormData()) // => true
isFormData({})             // => false
isFormData(null)           // => false
```

*Filter FormData from a mixed array*

Use as a predicate in .filter() to extract FormData values.

```typescript
const fd = new FormData();
fd.append('name', 'Alice');
const values = [fd, {}, new FormData(), 'text'];
values.filter(isFormData)
// => [FormData, FormData]
```

---

### `isFunction`

Checks if a value is a function.

```typescript
import { isFunction } from '@helpers4/guard';

isFunction(value: unknown): value is Function
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Function` — True if value is a function

**Examples:**

*isFunction*

```typescript
```ts
isFunction(() => {})       // => true
isFunction(function() {})  // => true
isFunction('function')     // => false
```
```

---

### `isGenerator`

Checks if a value is a generator object (the result of calling a `function*`).

Distinct from isGeneratorFunction: this predicate targets the
*instance* produced by calling a generator function, not the function itself.

```typescript
import { isGenerator } from '@helpers4/guard';

isGenerator(value: unknown): value is Generator<unknown, unknown, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Generator<unknown, unknown, unknown>` — `true` if value is a Generator instance

**Examples:**

*Distinguish a generator instance from its function*

isGenerator targets the object returned by calling a function*, not the function itself.

```typescript
function* counter() { yield 1; yield 2; }
isGenerator(counter())  // => true   (instance)
isGenerator(counter)    // => false  (function)
isGenerator([1, 2])     // => false
```

*Type-narrow to safely call .next()*

Narrows the type to Generator so you can call .next() and .return().

```typescript
function* gen() { yield 1; yield 2; }
const value: unknown = gen();
if (isGenerator(value)) {
  const { value: v, done } = value.next();
  // v: unknown, done: boolean | undefined
}
```

---

### `isGeneratorFunction`

Checks if a value is a generator function (a `function*` declaration or expression).

Distinct from isGenerator: this predicate targets the *function* itself,
not the iterator it produces when called.

```typescript
import { isGeneratorFunction } from '@helpers4/guard';

isGeneratorFunction(value: unknown): value is GeneratorFunction
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is GeneratorFunction` — `true` if value is a GeneratorFunction

**Examples:**

*Detect a generator function*

Returns true for function* declarations and expressions.

```typescript
function* gen() { yield 1; }
isGeneratorFunction(gen)      // => true
isGeneratorFunction(gen())    // => false  (instance, not function)
isGeneratorFunction(() => {}) // => false
```

*Filter generator factories from a mixed array*

Use as a predicate to select only generator functions.

```typescript
const fns = [() => {}, function* () { yield 1; }, async () => {}];
fns.filter(isGeneratorFunction)
// => [function* () { yield 1; }]
```

---

### `isIterable`

Checks if a value is iterable (has a `Symbol.iterator` method).

Returns `true` for strings, arrays, Maps, Sets, generators, and any object
implementing the iterable protocol.

```typescript
import { isIterable } from '@helpers4/guard';

isIterable(value: unknown): value is Iterable<unknown, any, any>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Iterable<unknown, any, any>` — True if value is iterable

**Examples:**

*isIterable*

```typescript
```ts
isIterable([1, 2, 3])      // => true
isIterable('hello')        // => true
isIterable(new Map())      // => true
isIterable(new Set())      // => true
isIterable({})             // => false
isIterable(42)             // => false
```
```

---

### `isJSON`

Checks whether a value is a string containing valid, parseable JSON text.

Distinct from isJSONValue, which checks an already-parsed runtime value's *shape*
— this checks a *string* before you parse it. Pairs naturally with `@helpers4/object`'s
`safeJsonParse`, which is the safe-parse counterpart once you know the string is valid —
this helper reuses it internally rather than re-implementing the parse/catch itself.

```typescript
import { isJSON } from '@helpers4/guard';

isJSON(value: unknown): value is string
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is string` — `true` if value is a string and `JSON.parse` succeeds on it

**Examples:**

*Check a string before parsing it*

Validates that a string is parseable JSON before calling JSON.parse.

```typescript
isJSON('{"a":1}')
// => true
isJSON('not json')
// => false
```

---

### `isJSONArray`

Checks whether a value is an array whose every element is a valid JSON value
(see isJSONValue).

```typescript
import { isJSONArray } from '@helpers4/guard';

isJSONArray(value: unknown): value is unknown[]
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is unknown[]` — `true` if value is an array of JSON values

**Examples:**

*Validate a JSON array*

Checks that a value is an array whose every element is JSON-representable.

```typescript
isJSONArray([1, 'two', null])
// => true
isJSONArray([1, undefined])
// => false
```

---

### `isJSONObject`

Checks whether a value is a plain object whose every own value is a valid JSON value
(see isJSONValue).

```typescript
import { isJSONObject } from '@helpers4/guard';

isJSONObject(value: unknown): value is Record<string, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Record<string, unknown>` — `true` if value is a plain object of JSON values

**Examples:**

*Validate a JSON object*

Checks that a value is a plain object whose every value is JSON-representable.

```typescript
isJSONObject({ a: 1, b: 'two' })
// => true
isJSONObject({ a: undefined })
// => false
```

---

### `isJSONValue`

Checks whether a value is composed entirely of JSON-representable types: `string`, finite
`number`, `boolean`, `null`, arrays of JSON values, or plain objects of JSON values.
`undefined`, functions, symbols, `NaN`/`Infinity`, and non-plain objects (`Date`, `Map`,
`Set`, class instances...) all return `false`, since none survive a real `JSON.stringify` /
`JSON.parse` round-trip unchanged. Circular references also return `false` for the same
reason, instead of recursing forever.

```typescript
import { isJSONValue } from '@helpers4/guard';

isJSONValue(value: unknown): boolean
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `boolean` — `true` if the value is a valid JSON value

**Examples:**

*Validate a value before sending it as JSON*

Recursively checks that a value only contains JSON-representable types.

```typescript
isJSONValue({ a: [1, 'two', null, { b: true }] })
// => true
isJSONValue({ a: new Date() })
// => false
```

---

### `isLength`

Checks whether a value is a valid array-like `length`: a non-negative safe integer
(`0 <= value <= Number.MAX_SAFE_INTEGER`).

```typescript
import { isLength } from '@helpers4/guard';

isLength(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — `true` if value is a valid length

**Examples:**

*Validate an array-like length before indexing*

Guards against negative, fractional, or unsafe length values.

```typescript
isLength(3)   // => true
isLength(-1)  // => false
isLength(1.5) // => false
```

---

### `isMap`

Checks if a value is a Map instance.

```typescript
import { isMap } from '@helpers4/guard';

isMap(value: unknown): value is Map<unknown, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Map<unknown, unknown>` — True if value is a Map

**Examples:**

*isMap*

```typescript
```ts
isMap(new Map())           // => true
isMap(new Map([['a', 1]])) // => true
isMap({})                  // => false
```
```

---

### `isNode`

Checks whether the code is currently running in a Node.js-like environment
(`process.versions.node` is defined — also true in Electron's Node context).

Note: some hybrid test environments (e.g. happy-dom, jsdom) define both `window` and Node's
`process` at the same time — this only reports on `process.versions.node`'s presence, it does
not imply `isBrowser` is false. Use both together if you need to distinguish plain Node.js
from a DOM-emulating Node test environment.

```typescript
import { isNode } from '@helpers4/guard';

isNode(): boolean
```

**Returns:** `boolean` — `true` if `process.versions.node` is defined

**Examples:**

*Branch on the runtime environment*

Useful for isomorphic code that behaves differently on the server vs. the browser.

```typescript
if (isNode()) {
  const fs = await import('node:fs')
  // server-side file access
}
```

---

### `isNull`

Checks if a value is `null`.

```typescript
import { isNull } from '@helpers4/guard';

isNull(value: unknown): value is null
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is null` — True if value is null

**Examples:**

*isNull*

```typescript
```ts
isNull(null)      // => true
isNull(undefined) // => false
isNull(0)         // => false
```
```

---

### `isNullish`

Checks if a value is null or undefined (nullish).

```typescript
import { isNullish } from '@helpers4/guard';

isNullish(value: unknown): value is null | undefined
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is null | undefined` — True if value is null or undefined

**Examples:**

*Check for null or undefined*

Returns true only for null and undefined, not other falsy values.

```typescript
isNullish(null)      // => true
isNullish(undefined) // => true
isNullish(0)         // => false
isNullish('')        // => false
```

*Guard before accessing properties*

Use as a type guard to safely narrow types.

```typescript
function greet(name: string | null | undefined): string {
  if (isNullish(name)) return 'Hello, stranger!';
  return `Hello, ${name}!`;
}
greet(null) // => 'Hello, stranger!'
```

---

### `isNumber`

Checks if a value is a number.

Returns `false` for `NaN`, which intentionally deviates from `typeof` behavior
to increase user-friendliness.

```typescript
import { isNumber } from '@helpers4/guard';

isNumber(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a number (excludes NaN)

**Examples:**

*isNumber*

```typescript
```ts
isNumber(42)    // => true
isNumber(0)     // => true
isNumber(NaN)   // => false
isNumber('123') // => false
```
```

---

### `isPlainObject`

Checks if a value is a plain object.

A plain object is created by `{}`, `new Object()`, or `Object.create(null)`.
Returns `false` for arrays, Date, Map, Set, RegExp, class instances, etc.

```typescript
import { isPlainObject } from '@helpers4/guard';

isPlainObject(value: unknown): value is Record<string, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Record<string, unknown>` — True if value is a plain object

**Examples:**

*isPlainObject*

```typescript
```ts
isPlainObject({})           // => true
isPlainObject({ a: 1 })    // => true
isPlainObject(new Date())  // => false
isPlainObject([])          // => false
isPlainObject(null)        // => false
```
```

---

### `isPrimitive`

Checks if a value is a JavaScript primitive.

Primitive types: `string`, `number`, `boolean`, `bigint`, `symbol`, `null`, `undefined`.

```typescript
import { isPrimitive } from '@helpers4/guard';

isPrimitive(value: unknown): value is Primitive
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Primitive` — True if value is a primitive

**Examples:**

*isPrimitive*

```typescript
```ts
isPrimitive('hello')  // => true
isPrimitive(42)       // => true
isPrimitive(null)     // => true
isPrimitive({})       // => false
isPrimitive([])       // => false
```
```

---

### `isPromise`

Checks if a value is a Promise or a thenable.

Returns `true` for any object that has `.then()` and `.catch()` methods,
including native Promises and userland implementations.

```typescript
import { isPromise } from '@helpers4/guard';

isPromise(value: unknown): value is PromiseLike<unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PromiseLike<unknown>` — True if value is a Promise-like object

**Examples:**

*isPromise*

```typescript
```ts
isPromise(Promise.resolve(42))     // => true
isPromise(new Promise(() => {}))   // => true
isPromise({ then: () => {} })      // => false (no .catch)
isPromise(42)                      // => false
```
```

---

### `isPromiseLike`

Checks if a value is a thenable (has a `.then()` method).

Looser than isPromise: accepts any object or function with a `then`
method, including non-standard Promise implementations without `.catch()`.
Follows the Promise/A+ specification for thenables.

```typescript
import { isPromiseLike } from '@helpers4/guard';

isPromiseLike(value: unknown): value is PromiseLike<unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PromiseLike<unknown>` — `true` if value is a PromiseLike (thenable)

**Examples:**

*Detect any thenable*

Returns true for native Promises and any object with a .then() method.

```typescript
isPromiseLike(Promise.resolve(1))    // => true
isPromiseLike({ then: () => {} })    // => true   (thenable)
isPromiseLike(42)                    // => false
isPromiseLike(null)                  // => false
isPromiseLike({ then: 'not-a-fn' }) // => false
```

*Handle both Promises and thenables in a utility*

Use isPromiseLike to accept any thenable, not just native Promises.

```typescript
function toPromise<T>(value: T | PromiseLike<T>): Promise<T> {
  if (isPromiseLike(value)) return Promise.resolve(value);
  return Promise.resolve(value);
}
```

---

### `isPropertyKey`

Checks if a value is a valid property key: `string`, `number`, or `symbol`.

```typescript
import { isPropertyKey } from '@helpers4/guard';

isPropertyKey(value: unknown): value is PropertyKey
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PropertyKey` — `true` if value can be used as an object property key

**Examples:**

*Detect valid property keys*

Strings, numbers, and symbols are valid property keys.

```typescript
isPropertyKey('name')        // => true
isPropertyKey(42)            // => true
isPropertyKey(Symbol('id'))  // => true
isPropertyKey(null)          // => false
isPropertyKey(true)          // => false
```

*Safe dynamic property access*

Use as a guard before indexing an object with an unknown key.

```typescript
function get(obj: Record<PropertyKey, unknown>, key: unknown): unknown {
  if (isPropertyKey(key)) return obj[key];
  return undefined;
}
get({ a: 1 }, 'a')    // => 1
get({ a: 1 }, null)   // => undefined
```

---

### `isRegExp`

Checks if a value is a RegExp instance.

```typescript
import { isRegExp } from '@helpers4/guard';

isRegExp(value: unknown): value is RegExp
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is RegExp` — True if value is a RegExp

**Examples:**

*isRegExp*

```typescript
```ts
isRegExp(/abc/)          // => true
isRegExp(new RegExp('a')) // => true
isRegExp('abc')          // => false
```
```

---

### `isSet`

Checks if a value is a Set instance.

```typescript
import { isSet } from '@helpers4/guard';

isSet(value: unknown): value is Set<unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Set<unknown>` — True if value is a Set

**Examples:**

*Check whether a value is a Set*

Returns true only for real Set instances.

```typescript
isSet(new Set([1, 2, 3])) // => true
isSet([1, 2, 3])          // => false
```

*Type-safe narrowing in a conditional*

Inside the `if` branch, the value is narrowed to Set<unknown>.

```typescript
const value: unknown = new Set(['a', 'b']);
if (isSet(value)) {
  value.size // => 2, type-safe access
}
```

---

### `isSpecialObject`

Determines if a value is a special object that should not have its properties compared deeply.
Special objects include: Date, Function, Promise, Observable, RegExp, Error, Map, Set, WeakMap, WeakSet, etc.

```typescript
import { isSpecialObject } from '@helpers4/guard';

isSpecialObject(value: unknown): boolean
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `boolean` — `true` if the value is a special object, `false` otherwise

**Examples:**

*Detect special objects*

Returns true for built-in objects like Date, Map, Set, RegExp, etc.

```typescript
isSpecialObject(new Date())     // => true
isSpecialObject(new Map())      // => true
isSpecialObject(/regex/)        // => true
```

*Plain objects are not special*

Returns false for plain objects and arrays.

```typescript
isSpecialObject({ a: 1 })  // => false
isSpecialObject([1, 2])    // => false
```

---

### `isString`

Checks if a value is a string.

```typescript
import { isString } from '@helpers4/guard';

isString(value: unknown): value is string
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is string` — True if value is a string

**Examples:**

*isString*

```typescript
```ts
isString('hello') // => true
isString(123)     // => false
```
```

---

### `isSymbol`

Checks if a value is a symbol.

```typescript
import { isSymbol } from '@helpers4/guard';

isSymbol(value: unknown): value is symbol
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is symbol` — True if value is a symbol

**Examples:**

*isSymbol*

```typescript
```ts
isSymbol(Symbol('test')) // => true
isSymbol(Symbol.iterator) // => true
isSymbol('symbol')       // => false
```
```

---

### `isTemporalDuration`

Checks if a value is a `Temporal.Duration`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalDuration } from '@helpers4/guard';

isTemporalDuration(value: unknown): value is Duration
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Duration` — True if value is a `Temporal.Duration`

**Examples:**

*isTemporalDuration*

```typescript
```ts
isTemporalDuration(Temporal.Duration.from({ hours: 1 }))  // => true
isTemporalDuration(Temporal.Now.instant())                 // => false
isTemporalDuration(1000)                                   // => false
```
```

---

### `isTemporalInstant`

Checks if a value is a `Temporal.Instant`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalInstant } from '@helpers4/guard';

isTemporalInstant(value: unknown): value is Instant
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Instant` — True if value is a `Temporal.Instant`

**Examples:**

*isTemporalInstant*

```typescript
```ts
isTemporalInstant(Temporal.Now.instant())      // => true
isTemporalInstant(Temporal.Now.plainDateISO())  // => false
isTemporalInstant(new Date())                   // => false
```
```

---

### `isTemporalPlainDate`

Checks if a value is a `Temporal.PlainDate`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalPlainDate } from '@helpers4/guard';

isTemporalPlainDate(value: unknown): value is PlainDate
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PlainDate` — True if value is a `Temporal.PlainDate`

**Examples:**

*isTemporalPlainDate*

```typescript
```ts
isTemporalPlainDate(Temporal.PlainDate.from('2025-01-19'))  // => true
isTemporalPlainDate(Temporal.Now.instant())                 // => false
isTemporalPlainDate(new Date())                             // => false
```
```

---

### `isTemporalPlainDateTime`

Checks if a value is a `Temporal.PlainDateTime`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalPlainDateTime } from '@helpers4/guard';

isTemporalPlainDateTime(value: unknown): value is PlainDateTime
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PlainDateTime` — True if value is a `Temporal.PlainDateTime`

**Examples:**

*isTemporalPlainDateTime*

```typescript
```ts
isTemporalPlainDateTime(Temporal.PlainDateTime.from('2025-01-19T12:00'))  // => true
isTemporalPlainDateTime(Temporal.Now.instant())                           // => false
isTemporalPlainDateTime(new Date())                                       // => false
```
```

---

### `isTemporalPlainTime`

Checks if a value is a `Temporal.PlainTime`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalPlainTime } from '@helpers4/guard';

isTemporalPlainTime(value: unknown): value is PlainTime
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is PlainTime` — True if value is a `Temporal.PlainTime`

**Examples:**

*isTemporalPlainTime*

```typescript
```ts
isTemporalPlainTime(Temporal.PlainTime.from('12:30:00'))  // => true
isTemporalPlainTime(Temporal.Now.instant())               // => false
isTemporalPlainTime(new Date())                           // => false
```
```

---

### `isTemporalZonedDateTime`

Checks if a value is a `Temporal.ZonedDateTime`.

Uses `instanceof` when `Temporal` is available globally, and falls back
to `Symbol.toStringTag` for environments without Temporal (e.g. browsers).

```typescript
import { isTemporalZonedDateTime } from '@helpers4/guard';

isTemporalZonedDateTime(value: unknown): value is ZonedDateTime
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is ZonedDateTime` — True if value is a `Temporal.ZonedDateTime`

**Examples:**

*isTemporalZonedDateTime*

```typescript
```ts
isTemporalZonedDateTime(Temporal.Now.zonedDateTimeISO())  // => true
isTemporalZonedDateTime(Temporal.Now.instant())           // => false
isTemporalZonedDateTime(new Date())                       // => false
```
```

---

### `isTimestamp`

Checks if a value is a valid timestamp (milliseconds or Unix seconds).

Supports:
- JavaScript / Java timestamps (milliseconds since epoch)
- Unix timestamps (seconds since epoch)

The function uses a heuristic to distinguish between the two:
numbers ≤ ~7.26 billion are treated as seconds, larger as milliseconds.

```typescript
import { isTimestamp } from '@helpers4/guard';

isTimestamp(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a number that represents a valid timestamp

**Examples:**

*isTimestamp*

```typescript
```ts
isTimestamp(1609459200000) // => true (JS ms — 2021-01-01)
isTimestamp(1609459200)    // => true (Unix seconds — 2021-01-01)
isTimestamp(Date.now())    // => true
isTimestamp(NaN)           // => false
isTimestamp('1609459200')  // => false (not a number)
```
```

---

### `isTruthy`

Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`).

This is the type-safe alternative to `Boolean()` as a filter callback.
Unlike `filter(Boolean)`, using `filter(isTruthy)` correctly narrows the
resulting array type by excluding falsy values.

```typescript
import { isTruthy } from '@helpers4/guard';

isTruthy<T>(value: Falsy | T): value is T
```

**Parameters:**

- `value: Falsy | T` — The value to check

**Returns:** `value is T` — True if the value is truthy

**Examples:**

*Check truthy values*

Returns true for all truthy values, false for falsy ones.

```typescript
isTruthy(1)         // => true
isTruthy('hello')   // => true
isTruthy(0)         // => false
isTruthy(null)      // => false
```

*Type-safe filter alternative to Boolean*

Use isTruthy with Array.filter to get correct TypeScript narrowing.

```typescript
const items = ['a', '', null, 'b', undefined];
const result = items.filter(isTruthy);
// => ['a', 'b'] with type string[]
```

---

### `isUndefined`

Checks if a value is `undefined`.

```typescript
import { isUndefined } from '@helpers4/guard';

isUndefined(value: unknown): value is undefined
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is undefined` — True if value is undefined

**Examples:**

*isUndefined*

```typescript
```ts
isUndefined(undefined) // => true
isUndefined(null)      // => false
isUndefined(0)         // => false
```
```

---

### `isValidRegex`

Checks if a string is a valid regex pattern.

```typescript
import { isValidRegex } from '@helpers4/guard';

isValidRegex(value: string): boolean
```

**Parameters:**

- `value: string` — The string to check

**Returns:** `boolean` — True if the string is a valid regex pattern

**Examples:**

*isValidRegex*

```typescript
```ts
isValidRegex('[a-z]+') // => true
isValidRegex('.*')     // => true
isValidRegex('[')      // => false
```
```

---

### `isWeakMap`

Checks if a value is a WeakMap instance.

```typescript
import { isWeakMap } from '@helpers4/guard';

isWeakMap(value: unknown): value is WeakMap<object, unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is WeakMap<object, unknown>` — True if value is a WeakMap

**Examples:**

*Check whether a value is a WeakMap*

Distinguishes WeakMap from the regular (iterable) Map.

```typescript
isWeakMap(new WeakMap()) // => true
isWeakMap(new Map())     // => false
```

*Plain objects are not WeakMaps*

Only real WeakMap instances pass, not objects that merely look like one.

```typescript
isWeakMap({ get: () => undefined, set: () => {} })
// => false
```

---

### `isWeakSet`

Checks if a value is a WeakSet instance.

```typescript
import { isWeakSet } from '@helpers4/guard';

isWeakSet(value: unknown): value is WeakSet<object>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is WeakSet<object>` — True if value is a WeakSet

**Examples:**

*Check whether a value is a WeakSet*

Distinguishes WeakSet from the regular (iterable) Set.

```typescript
isWeakSet(new WeakSet()) // => true
isWeakSet(new Set())     // => false
```

*Arrays are not WeakSets*

Only real WeakSet instances pass.

```typescript
isWeakSet([1, 2, 3])
// => false
```

---

## id

Package: `@helpers4/id`

### `uuid7`

Generates a UUID v7 string (RFC 9562).
UUID v7 embeds a Unix timestamp in milliseconds, making it
chronologically sortable while retaining randomness.

```typescript
import { uuid7 } from '@helpers4/id';

uuid7(): string
```

**Returns:** `string` — A UUID v7 string in the format `xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx`

**Examples:**

*Generate a UUID v7*

Produces a RFC 9562 UUID v7 string with an embedded millisecond timestamp.

```typescript
uuid7()
// => "019077e0-5c70-7b3a-8a1f-3e4d5b6c7d8e"
```

*UUIDs are chronologically sortable*

UUID v7 values generated later are lexicographically greater, making them ideal for database primary keys.

```typescript
const id1 = uuid7();
// ... later ...
const id2 = uuid7();
id1 < id2 // => true
```

*Each UUID is unique*

No two calls produce the same value.

```typescript
uuid7() !== uuid7() // => true
```

---

## map

Package: `@helpers4/map`

### `countBy`

Groups the entries of a Map by a derived key and counts how many fall into each group.

```typescript
import { countBy } from '@helpers4/map';

countBy<K, V, R>(map: ReadonlyMap<K, V>, fn: function): Map<R, number>
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to summarize
- `fn: function` — Function deriving the grouping key from (value, key)

**Returns:** `Map<R, number>` — A Map from derived key to the number of entries in that group

**Examples:**

*Count entries by parity*

Groups values by a derived key and counts how many fall into each group.

```typescript
countBy(new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4]]), v => v % 2 === 0 ? 'even' : 'odd')
// => Map(2) { 'odd' => 2, 'even' => 2 }
```

---

### `every`

Checks if every entry of a Map satisfies the predicate. Short-circuits on the first mismatch.

```typescript
import { every } from '@helpers4/map';

every<K, V>(map: ReadonlyMap<K, V>, predicate: function): boolean
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to check
- `predicate: function` — Function invoked with (value, key)

**Returns:** `boolean` — `true` if all entries match (vacuously `true` for an empty map), `false` otherwise

**Examples:**

*Check that all values are positive*

Returns false as soon as one entry fails the predicate.

```typescript
every(new Map([['a', 1], ['b', 2]]), value => value > 0)
// => true
```

*Empty map*

Vacuously true for an empty map.

```typescript
every(new Map(), () => false)
// => true
```

---

### `filter`

Creates a new Map containing only the entries for which the predicate returns true.

```typescript
import { filter } from '@helpers4/map';

filter<K, V>(map: ReadonlyMap<K, V>, predicate: function): Map<K, V>
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to filter
- `predicate: function` — Function invoked with (value, key) — return true to keep the entry

**Returns:** `Map<K, V>` — A new Map with only the matching entries

**Examples:**

*Keep only even values*

Creates a new Map with only the entries whose value satisfies the predicate.

```typescript
filter(new Map([['a', 1], ['b', 2], ['c', 3]]), value => value % 2 === 0)
// => Map(1) { 'b' => 2 }
```

*No matches*

Returns an empty Map when nothing matches.

```typescript
filter(new Map([['a', 1]]), () => false)
// => Map(0) {}
```

---

### `findKey`

Returns the first key of a Map whose entry satisfies the predicate, in insertion order.

```typescript
import { findKey } from '@helpers4/map';

findKey<K, V>(map: ReadonlyMap<K, V>, predicate: function): K | undefined
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to search
- `predicate: function` — Function invoked with (value, key)

**Returns:** `K | undefined` — The matching key, or `undefined` if none matches

**Examples:**

*Find the key of the first matching entry*

Searches in insertion order and returns the key of the first match.

```typescript
findKey(new Map([['a', 1], ['b', 2]]), value => value > 1)
// => 'b'
```

*No match*

Returns undefined when nothing satisfies the predicate.

```typescript
findKey(new Map([['a', 1]]), value => value > 10)
// => undefined
```

---

### `findValue`

Returns the first value of a Map whose entry satisfies the predicate, in insertion order.

```typescript
import { findValue } from '@helpers4/map';

findValue<K, V>(map: ReadonlyMap<K, V>, predicate: function): V | undefined
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to search
- `predicate: function` — Function invoked with (value, key)

**Returns:** `V | undefined` — The matching value, or `undefined` if none matches

**Examples:**

*Find the first matching value*

Searches in insertion order and returns the value of the first match.

```typescript
findValue(new Map([['a', 1], ['b', 2]]), value => value > 1)
// => 2
```

*No match*

Returns undefined when nothing satisfies the predicate.

```typescript
findValue(new Map([['a', 1]]), value => value > 10)
// => undefined
```

---

### `hasValue`

Checks whether a value exists anywhere in a Map (`Map.prototype.has` checks keys, not values).
Uses `Object.is`-style equality (via `===`, with `NaN` matching `NaN`).

```typescript
import { hasValue } from '@helpers4/map';

hasValue<K, V>(map: ReadonlyMap<K, V>, value: V): boolean
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to search
- `value: V` — The value to look for

**Returns:** `boolean` — `true` if the value is found in at least one entry

**Examples:**

*Check whether a value exists in a Map*

Unlike Map.prototype.has (which checks keys), this checks values.

```typescript
hasValue(new Map([['a', 1], ['b', 2]]), 2)
// => true
```

*Value absent*

Returns false when no entry has that value.

```typescript
hasValue(new Map([['a', 1]]), 99)
// => false
```

---

### `mapKeys`

Creates a new Map with the same values but with each key transformed by a function.
If two entries derive the same new key, the later one (in insertion order) wins.

```typescript
import { mapKeys } from '@helpers4/map';

mapKeys<K, V, R>(map: ReadonlyMap<K, V>, fn: function): Map<R, V>
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to transform
- `fn: function` — Function invoked with (key, value) that returns the new key

**Returns:** `Map<R, V>` — A new Map with transformed keys

**Examples:**

*Uppercase every key*

Creates a new Map with transformed keys and unchanged values.

```typescript
mapKeys(new Map([['a', 1], ['b', 2]]), key => key.toUpperCase())
// => Map(2) { 'A' => 1, 'B' => 2 }
```

---

### `mapValues`

Creates a new Map with the same keys but with each value transformed by a function.

```typescript
import { mapValues } from '@helpers4/map';

mapValues<K, V, R>(map: ReadonlyMap<K, V>, fn: function): Map<K, R>
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to transform
- `fn: function` — Function invoked with (value, key) that returns the new value

**Returns:** `Map<K, R>` — A new Map with transformed values

**Examples:**

*Scale every value*

Creates a new Map with transformed values and unchanged keys.

```typescript
mapValues(new Map([['a', 1], ['b', 2]]), value => value * 10)
// => Map(2) { 'a' => 10, 'b' => 20 }
```

---

### `reduce`

Reduces a Map to a single value by applying a function to each entry, in insertion order.

```typescript
import { reduce } from '@helpers4/map';

reduce<K, V, R>(map: ReadonlyMap<K, V>, fn: function, initial: R): R
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to reduce
- `fn: function` — Function invoked with (accumulator, value, key)
- `initial: R` — Initial accumulator value

**Returns:** `R` — The final accumulator value

**Examples:**

*Sum all values*

Reduces a Map of numbers to their total.

```typescript
reduce(new Map([['a', 1], ['b', 2], ['c', 3]]), (acc, value) => acc + value, 0)
// => 6
```

*Build an array of keys*

Reduce can produce a completely different shape than the map values.

```typescript
reduce(new Map([['a', 1], ['b', 2]]), (acc: string[], _v, key) => [...acc, key], [])
// => ['a', 'b']
```

---

### `some`

Checks if at least one entry of a Map satisfies the predicate. Short-circuits on the first match.

```typescript
import { some } from '@helpers4/map';

some<K, V>(map: ReadonlyMap<K, V>, predicate: function): boolean
```

**Parameters:**

- `map: ReadonlyMap<K, V>` — The Map to check
- `predicate: function` — Function invoked with (value, key)

**Returns:** `boolean` — `true` if at least one entry matches, `false` otherwise (including for an empty map)

**Examples:**

*Check if any value exceeds a threshold*

Returns true as soon as one entry satisfies the predicate.

```typescript
some(new Map([['a', 1], ['b', 2]]), value => value > 1)
// => true
```

*Empty map*

Always returns false for an empty map.

```typescript
some(new Map(), () => true)
// => false
```

---

### `toMapByKey`

Builds a Map from an iterable of items, keyed by a derived key. When two items derive the
same key, the later item wins (last-write-wins, matching `Map.prototype.set` semantics).

Named `toMapByKey`, not `keyBy` (lodash's name for the same idea) — lodash's `_.keyBy`
returns a plain object, not a `Map`; the `to<Type>` prefix (matching `toSorted`/`toReversed`)
makes the actual return type unambiguous.

```typescript
import { toMapByKey } from '@helpers4/map';

toMapByKey<T, K>(items: Iterable<T>, fn: function): Map<K, T>
```

**Parameters:**

- `items: Iterable<T>` — The items to index
- `fn: function` — Function deriving the key for each item

**Returns:** `Map<K, T>` — A Map from derived key to item

**Examples:**

*Index an array of records by id*

Builds a Map keyed by a derived value, for O(1) lookup by that key.

```typescript
toMapByKey([{ id: 'a', n: 1 }, { id: 'b', n: 2 }], item => item.id)
// => Map(2) { 'a' => {...}, 'b' => {...} }
```

*Last item wins on collision*

When two items derive the same key, the later one overwrites the earlier one.

```typescript
toMapByKey([{ id: 'a', n: 1 }, { id: 'a', n: 2 }], item => item.id).get('a')
// => { id: 'a', n: 2 }
```

---

## markdown

Package: `@helpers4/markdown`

### `escape`

Escapes all Markdown special characters in a string so they render as
literal text rather than formatting syntax.

Escaped characters: `\ \` * _ { } [ ] ( ) # + - . !`

Pass `{ cell: true }` to also escape pipe characters and replace newlines
with spaces, making the result safe for embedding in a Markdown table cell.

```typescript
import { escape } from '@helpers4/markdown';

escape(str: string, options?: EscapeOptions): string
```

**Parameters:**

- `str: string` — The raw string to escape
- `options?: EscapeOptions` — Optional escaping options

**Returns:** `string` — The escaped string

**Examples:**

*Escape special Markdown characters*

Prefixes every Markdown special character with a backslash.

```typescript
escape('**bold** and _italic_')
// => '\\*\\*bold\\*\\* and \\_italic\\_'
```

*Safely render user input inside Markdown*

Prevents user-supplied strings from breaking Markdown formatting.

```typescript
const userInput = '(C) [helpers4]';
const safe = escape(userInput);
// => '\\(C\\) \\[helpers4\\]'
```

---

## node

Package: `@helpers4/node`

### `isBuffer`

Checks if a value is a Node.js Buffer instance.

`Buffer` extends `Uint8Array` and is specific to Node.js, Bun, and Deno.
In browser-only environments where `Buffer` is not defined, this function
always returns `false`.

Useful for filtering or type-narrowing in a functional pipeline:
`values.filter(isBuffer)`

```typescript
import { isBuffer } from '@helpers4/node';

isBuffer(value: unknown): value is Buffer<ArrayBufferLike>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Buffer<ArrayBufferLike>` — True if value is a Buffer

**Examples:**

*Detect a Node.js Buffer*

Returns true only for Buffer instances. Uint8Array is not a Buffer.

```typescript
isBuffer(Buffer.from('hello')) // => true
isBuffer(new Uint8Array(8))    // => false
isBuffer('hello')              // => false
```

*Filter Buffers from a mixed array*

Use as a predicate in .filter() to extract Buffer values.

```typescript
const values = [Buffer.from('a'), 'text', Buffer.alloc(4), 42];
values.filter(isBuffer)
// => [Buffer, Buffer]
```

---

### `isNodeStream`

Checks if a value is a Node.js stream (has a `.pipe()` method).

Uses duck-typing: any object with a `pipe` function qualifies, covering
`Readable`, `Writable`, `Duplex`, `Transform`, and custom stream-compatible
objects without importing from `node:stream`.

```typescript
import { isNodeStream } from '@helpers4/node';

isNodeStream(value: unknown): value is object
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is object` — `true` if value is a Node.js stream

**Examples:**

*Detect a Node.js stream*

Returns true for any object with a .pipe() method (Readable, Writable, Transform, etc.).

```typescript
import { Readable } from 'node:stream';
isNodeStream(new Readable({ read() {} })) // => true
isNodeStream({})                          // => false
isNodeStream(null)                        // => false
```

*Guard before piping an unknown value*

Use isNodeStream to safely pipe only known streams.

```typescript
import { Writable } from 'node:stream';
function pipeToOutput(source: unknown, dest: Writable): void {
  if (isNodeStream(source)) {
    source.pipe(dest);
  }
}
```

---

### `isSharedArrayBuffer`

Checks if a value is a `SharedArrayBuffer` instance.

`SharedArrayBuffer` enables shared memory between the main thread and worker
threads. In browsers without COOP/COEP headers, `SharedArrayBuffer` may be
unavailable; this function returns `false` in that case.

```typescript
import { isSharedArrayBuffer } from '@helpers4/node';

isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is SharedArrayBuffer` — `true` if value is a SharedArrayBuffer

**Examples:**

*Distinguish SharedArrayBuffer from ArrayBuffer*

Returns true only for SharedArrayBuffer instances, not plain ArrayBuffers.

```typescript
isSharedArrayBuffer(new SharedArrayBuffer(8)) // => true
isSharedArrayBuffer(new ArrayBuffer(8))       // => false
isSharedArrayBuffer(null)                     // => false
```

*Safe shared memory check before worker communication*

Use as a guard to ensure a buffer can be transferred to a Worker.

```typescript
function sendToWorker(buffer: unknown): void {
  if (isSharedArrayBuffer(buffer)) {
    // buffer is SharedArrayBuffer — can be shared directly
    // worker.postMessage({ buffer });
  } else {
    // must transfer or copy
  }
}
```

---

## number

Package: `@helpers4/number`

### `clamp`

Clamps a number between min and max values

```typescript
import { clamp } from '@helpers4/number';

clamp(value: number, min: number, max: number): number
```

**Parameters:**

- `value: number` — The value to clamp
- `min: number` — Minimum value
- `max: number` — Maximum value

**Returns:** `number` — Clamped value

**Examples:**

*Clamp a value within range*

Restricts a number to be within a min/max range.

```typescript
clamp(15, 0, 10)  // => 10
clamp(-5, 0, 10)  // => 0
clamp(5, 0, 10)   // => 5
```

---

### `correctFloat`

Corrects floating-point arithmetic errors by rounding to a given number
of significant digits. Useful after calculations that accumulate binary
floating-point drift (e.g. `0.1 + 0.2 === 0.30000000000000004`).

The default precision of 14 significant digits eliminates typical
rounding noise for values in the range used by most applications.
Note: for values whose integer part already consumes 14 or more digits
(i.e. |value| ≥ 1e13), toPrecision(14) has no room left for decimal
digits and will silently truncate them. Increase `precision` if you
need to correct drift in very large numbers.

Note: IEEE-754 doubles carry at most ~17 significant decimal digits.
Precision values above 17 pad with digits that reflect the underlying
binary representation rather than correcting drift.

```typescript
import { correctFloat } from '@helpers4/number';

correctFloat(value: number, precision: number): number
```

**Parameters:**

- `value: number` — The floating-point value to correct
- `precision: number` (default: `14`) — Integer number of significant digits between 1 and 100
  (default: 14). Values above 17 are valid but expose binary noise beyond
  IEEE-754's meaningful range.

**Returns:** `number` — The corrected value

**Examples:**

*Fix floating-point drift*

Corrects the classic 0.1 + 0.2 floating-point arithmetic error.

```typescript
0.1 + 0.2              // => 0.30000000000000004
correctFloat(0.1 + 0.2)  // => 0.3
```

*Custom significant-digit precision*

Pass a second argument to control how many significant digits to keep.

```typescript
correctFloat(1.23456789, 4)  // => 1.235
correctFloat(1.23456789, 6)  // => 1.23457
```

---

### `extractNumber`

Extracts the first number embedded anywhere in a string, or passes through a `number`.

Unlike a plain `parseFloat`/`parseInt`, the number does not need to be at the start of
the string: digits are searched for anywhere, so leading/trailing text (units, labels, ...)
is ignored. A `-` before the digits and a scientific-notation suffix (`e`/`E`) are
disambiguated with ExtractNumberOptions.sign and ExtractNumberOptions.exponent.

Returns `undefined` if no number can be found.

```typescript
import { extractNumber } from '@helpers4/number';

extractNumber(value: unknown, options: ExtractNumberOptions): number | undefined
```

**Parameters:**

- `value: unknown` — The value to extract a number from
- `options: ExtractNumberOptions` (default: `{}`) — Options controlling sign and exponent disambiguation

**Returns:** `number | undefined` — The extracted number, or `undefined` if none was found

**Examples:**

*extractNumber*

```typescript
```ts
extractNumber('16.5px')        // => 16.5
extractNumber('.5rem')         // => 0.5   (leading-dot decimal)
extractNumber('-.5')           // => -0.5  (leading-dot with sign)
extractNumber('Wafer 10')      // => 10
extractNumber('xxx-111')       // => 111   ('-' glued to text → separator)
extractNumber('xxx -111')      // => -111  ('-' preceded by a space → sign)
extractNumber('x-.5')          // => 0.5   ('-' glued to 'x' → separator; leading-dot decimal follows)
extractNumber('-111')          // => -111  ('-' at the start of the string → sign)
extractNumber('1e5 mol')       // => 100000
extractNumber('1e5kg')         // => 1     ('e5' glued to text → mantissa only)
extractNumber('no number')     // => undefined
extractNumber(42)              // => 42
```
```

---

### `formatCompact`

Formats a number using compact notation (e.g. `1_500_000 → "1.5M"`).

Thin wrapper over `Intl.NumberFormat` with `notation: 'compact'`. Companion
of `formatSize` in the same `format*` family.

```typescript
import { formatCompact } from '@helpers4/number';

formatCompact(value: number, locale?: string): string
```

**Parameters:**

- `value: number` — The number to format.
- `locale?: string` — BCP 47 locale tag. Defaults to the runtime locale.

**Returns:** `string` — A compact string representation of the number.

**Examples:**

*Compact large numbers*

Formats a number using K / M suffixes for readability.

```typescript
formatCompact(1_500_000, 'en') // => '1.5M'
formatCompact(1_000, 'en')     // => '1K'
formatCompact(999, 'en')       // => '999'
```

*Locale-aware formatting*

Uses the provided locale for the decimal separator and suffix.

```typescript
formatCompact(1_500_000, 'fr') // => '1,5 M'
```

---

### `formatSize`

Format a byte count into a human-readable string with the appropriate unit.

Each unit is 1024 of the previous (binary prefix). The result is formatted
with one decimal place.

```typescript
import { formatSize } from '@helpers4/number';

formatSize(bytes: number): string
```

**Parameters:**

- `bytes: number` — A non-negative integer representing a byte count.

**Returns:** `string` — A human-readable string such as `'0.0B'`, `'1.5KB'`, `'3.2MB'`.

**Examples:**

*Format bytes to human-readable size*

Converts a raw byte count to a human-readable string using binary prefixes.

```typescript
formatSize(0)             // '0.0B'
formatSize(512)           // '512.0B'
formatSize(1024)          // '1.0KB'
formatSize(1_048_576)     // '1.0MB'
formatSize(1_073_741_824) // '1.0GB'
```

---

### `inRange`

Checks whether a number falls within `[min, max]` (both inclusive by default).

```typescript
import { inRange } from '@helpers4/number';

inRange(value: number, min: number, max: number, options: InRangeOptions): boolean
```

**Parameters:**

- `value: number` — The number to test
- `min: number` — Lower bound
- `max: number` — Upper bound
- `options: InRangeOptions` (default: `{}`) — Boundary inclusion mode (default: `'both'`)

**Returns:** `boolean` — `true` if `value` is within the specified range

**Examples:**

*Check if a value is within bounds (inclusive)*

Both min and max are included by default.

```typescript
inRange(5, 1, 10)   // => true
inRange(0, 1, 10)   // => false
inRange(1, 1, 10)   // => true  (min included)
inRange(10, 1, 10)  // => true  (max included)
```

*Exclusive range*

Use { inclusive: "none" } for open interval (min, max).

```typescript
inRange(5, 1, 10, { inclusive: 'none' })  // => true
inRange(1, 1, 10, { inclusive: 'none' })  // => false
inRange(10, 1, 10, { inclusive: 'none' }) // => false
```

---

### `isEven`

Checks if a value is an even integer.

Returns `false` for non-numbers, non-integers, `NaN`, `Infinity`, and odd integers.

```typescript
import { isEven } from '@helpers4/number';

isEven(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — `true` if value is an integer divisible by 2

**Examples:**

*Check if a number is even*

Returns true for integers divisible by 2, false otherwise.

```typescript
isEven(4)   // => true
isEven(0)   // => true
isEven(3)   // => false
isEven(1.5) // => false  (not an integer)
```

*Filter even numbers from an array*

Use as a predicate in .filter() to extract even integers.

```typescript
const nums = [1, 2, 3, 4, 5, 6];
nums.filter(isEven)
// => [2, 4, 6]
```

---

### `isNegative`

Checks if a value is a number less than 0.

Returns `false` for `NaN`, `0`, positive numbers, and non-number types.

```typescript
import { isNegative } from '@helpers4/number';

isNegative(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a negative number

**Examples:**

*isNegative*

```typescript
```ts
isNegative(-1)        // => true
isNegative(-0.5)      // => true
isNegative(-Infinity) // => true
isNegative(0)         // => false
isNegative(1)         // => false
isNegative(NaN)       // => false
```
```

---

### `isOdd`

Checks if a value is an odd integer.

Returns `false` for non-numbers, non-integers, `NaN`, `Infinity`, and even integers.

```typescript
import { isOdd } from '@helpers4/number';

isOdd(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — `true` if value is an integer not divisible by 2

**Examples:**

*Check if a number is odd*

Returns true for integers not divisible by 2, false otherwise.

```typescript
isOdd(3)   // => true
isOdd(1)   // => true
isOdd(2)   // => false
isOdd(0)   // => false
isOdd(1.5) // => false  (not an integer)
```

*Filter odd numbers from an array*

Use as a predicate in .filter() to extract odd integers.

```typescript
const nums = [1, 2, 3, 4, 5, 6];
nums.filter(isOdd)
// => [1, 3, 5]
```

---

### `isPositive`

Checks if a value is a number greater than 0.

Returns `false` for `NaN`, `0`, negative numbers, and non-number types.

```typescript
import { isPositive } from '@helpers4/number';

isPositive(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a positive number

**Examples:**

*isPositive*

```typescript
```ts
isPositive(42)       // => true
isPositive(0.1)      // => true
isPositive(Infinity) // => true
isPositive(0)        // => false
isPositive(-1)       // => false
isPositive(NaN)      // => false
```
```

---

### `lerp`

Linearly interpolates between `start` and `end` by the factor `t`.

- `t = 0` returns `start`.
- `t = 1` returns `end`.
- Values of `t` outside `[0, 1]` extrapolate beyond the range.

```typescript
import { lerp } from '@helpers4/number';

lerp(start: number, end: number, t: number): number
```

**Parameters:**

- `start: number` — The start value.
- `end: number` — The end value.
- `t: number` — The interpolation factor.

**Returns:** `number` — The interpolated value.

**Examples:**

*Interpolate between two values*

Returns the value between start and end at position t (0 = start, 1 = end).

```typescript
lerp(0, 100, 0)    // => 0
lerp(0, 100, 0.5)  // => 50
lerp(0, 100, 1)    // => 100
```

*Animate a colour channel*

t outside [0, 1] extrapolates beyond the range.

```typescript
lerp(0, 255, 0.5) // => 127.5
lerp(0, 10, 2)    // => 20  (extrapolation)
```

---

### `randomBetween`

Generates a random number between min and max (inclusive)

```typescript
import { randomBetween } from '@helpers4/number';

randomBetween(min: number, max: number): number
```

**Parameters:**

- `min: number` — Minimum value
- `max: number` — Maximum value

**Returns:** `number` — Random number between min and max

**Examples:**

*Generate a random float in range*

Returns a random number between min and max (inclusive).

```typescript
randomBetween(1, 10)
// => e.g. 5.327...
```

*Generate a random integer in range*

Returns a random integer between min and max (inclusive).

```typescript
randomIntBetween(1, 6)
// => e.g. 4
```

---

### `randomIntBetween`

Generates a random integer between min and max (inclusive)

```typescript
import { randomIntBetween } from '@helpers4/number';

randomIntBetween(min: number, max: number): number
```

**Parameters:**

- `min: number` — Minimum value
- `max: number` — Maximum value

**Returns:** `number` — Random integer between min and max

---

### `roundTo`

Rounds a number to specified decimal places

```typescript
import { roundTo } from '@helpers4/number';

roundTo(value: number, decimals: number): number
```

**Parameters:**

- `value: number` — The number to round
- `decimals: number` — Number of decimal places

**Returns:** `number` — Rounded number

**Examples:**

*Round to 2 decimal places*

Rounds a floating-point number to the specified number of decimals.

```typescript
roundTo(3.14159, 2)
// => 3.14
```

*Round to 0 decimal places*

Effectively rounds to the nearest integer.

```typescript
roundTo(3.7, 0)
// => 4
```

---

## object

Package: `@helpers4/object`

### `camelCaseKeys`

Recursively transforms every key of a plain object (including keys nested inside arrays and
nested objects) to camelCase. Handles snake_case, kebab-case, PascalCase, and space-separated
keys. Non-plain-object values (arrays' items aside) — `Date`, `Map`, `Set`, class instances,
primitives — are left untouched, only their position is walked into. An entry whose
transformed key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) is
silently skipped, same as the rest of `@helpers4/object`.

```typescript
import { camelCaseKeys } from '@helpers4/object';

camelCaseKeys<T>(value: T): T
```

**Parameters:**

- `value: T` — The object (or array of objects) to transform

**Returns:** `T` — A new value with every plain-object key converted to camelCase

**Examples:**

*Convert a snake_case API response*

Recursively converts every key, including nested objects, to camelCase.

```typescript
camelCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
// => { userName: 'Alice', homeAddress: { zipCode: '12345' } }
```

*Arrays of objects are walked too*

Each object inside an array gets its keys converted.

```typescript
camelCaseKeys({ user_list: [{ first_name: 'A' }] })
// => { userList: [{ firstName: 'A' }] }
```

---

### `clone`

Creates a shallow copy of a value — one level deep, unlike cloneDeep.

Unlike a plain `{ ...value }` spread, this correctly reconstructs `Date`,
`Map`, `Set`, and arrays instead of producing an empty (or wrong-shaped)
plain object for them. Primitives are returned as-is. Any other object
(including class instances not listed above) has its own enumerable string
keys shallow-copied into a plain object — the same fallback `cloneDeep`
uses, so the two stay consistent for types neither one special-cases.

```typescript
import { clone } from '@helpers4/object';

clone<T>(value: T): T
```

**Parameters:**

- `value: T` — The value to shallow-clone

**Returns:** `T` — A shallow copy of `value`

**Examples:**

*Shallow-copy a plain object*

Top-level keys are copied; nested values still share references (shallow).

```typescript
const obj = { a: 1, b: { c: 2 } };
const copy = clone(obj);
copy.b === obj.b // => true (same nested reference)
```

*Correctly clones Date/Map/Set, unlike a spread*

{ ...new Date() } produces {} — clone() doesn't have that problem.

```typescript
clone(new Map([['a', 1]]))
// => new Map with the same entries, not {}
```

---

### `cloneDeep`

Creates a deep copy of an object or array.

Recursively clones own enumerable string keys. `Date` instances are
reconstructed with the same epoch value. Prototype-polluting keys
(`__proto__`, `constructor`, `prototype`) are silently skipped.
Does **not** handle circular references.

**Limitation:** symbol-keyed own properties are **not** copied — only string
keys are processed. Use `structuredClone` if symbol propagation is required.

```typescript
import { cloneDeep } from '@helpers4/object';

cloneDeep<T>(obj: T): T
```

**Parameters:**

- `obj: T` — The value to clone

**Returns:** `T` — A deep copy of `obj`

**Examples:**

*Clone a nested object*

Creates a deep copy — modifying the clone does not affect the original.

```typescript
const original = { a: { b: 1 } };
const cloned = cloneDeep(original);
cloned.a.b = 2;
// original.a.b is still 1
```

---

### `compact`

Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object.
Own keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are
silently skipped.

```typescript
import { compact } from '@helpers4/object';

compact<T extends Record<string, unknown>>(obj: T): Partial<T>
```

**Parameters:**

- `obj: T` — The source object

**Returns:** `Partial<T>` — A new object containing only entries with truthy values

```typescript
import { compact } from '@helpers4/object';

compact(obj: undefined): undefined
```

**Parameters:**

- `obj: undefined` — The source object

**Returns:** `undefined` — A new object containing only entries with truthy values

```typescript
import { compact } from '@helpers4/object';

compact(obj: null): null
```

**Parameters:**

- `obj: null` — The source object

**Returns:** `null` — A new object containing only entries with truthy values

**Examples:**

*Remove falsy values from object*

Removes all entries with falsy values (false, null, undefined, 0, "", NaN).

```typescript
compact({ a: 1, b: null, c: '', d: 0, e: 'hello' })
// => { a: 1, e: 'hello' }
```

*Clean up API response*

Useful to strip empty or missing fields before sending data.

```typescript
compact({ name: 'Alice', email: '', age: 0, role: 'admin' })
// => { name: 'Alice', role: 'admin' }
```

---

### `diff`

Structural object diff.

Returns `true` when both inputs are deeply equal, otherwise a
DiffResult describing the differences key by key.

Comparison rules:
- Same reference \u2192 `true`.
- Either side is `null`/`undefined` (and not both) \u2192 `false`.
- Both `Date` \u2192 epoch comparison.
- Both arrays \u2192 compared with `array/equalsDeep` (leaf, no diff drill-down).
- Special objects (Map, Set, RegExp, Promise, class instances\u2026) \u2192 reference equality.
- Plain objects \u2192 key-by-key, recursing up to `options.depth` levels.
- Mixed types (e.g. array vs object, Date vs object) \u2192 `false`.

For a boolean wrapper see equalsDeep from this category.
For a one-level boolean check see equalsShallow from this category.

```typescript
import { diff } from '@helpers4/object';

diff(objA: object | null | undefined, objB: object | null | undefined, options: DiffOptions): boolean | DiffResult
```

**Parameters:**

- `objA: object | null | undefined` — First value (object, `null`, or `undefined`).
- `objB: object | null | undefined` — Second value (object, `null`, or `undefined`).
- `options: DiffOptions` (default: `{}`) — See DiffOptions.

**Returns:** `boolean | DiffResult` — `true` when equal, otherwise a DiffResult, or `false` for incompatible types.

**Examples:**

*Compare nested objects*

Deeply compares two objects, returning true when they are structurally equal.

```typescript
diff({ a: { b: 1 } }, { a: { b: 1 } })
// => true
```

*Detect deep differences*

Returns a detailed diff object when nested values differ.

```typescript
diff({ a: { b: 1 } }, { a: { b: 2 } })
// => { a: { b: false } }
```

---

### `equalsDeep`

Recursive structural object equality.

Boolean wrapper around diff \u2014 returns `true` when the two values
are deeply equal according to the same rules. Use this when you only
need a yes/no answer; use diff when you also need to know
*what* differs.

For a one-level boolean check use equalsShallow.

```typescript
import { equalsDeep } from '@helpers4/object';

equalsDeep(objA: object | null | undefined, objB: object | null | undefined): boolean
```

**Parameters:**

- `objA: object | null | undefined` — First value (object, `null`, or `undefined`).
- `objB: object | null | undefined` — Second value (object, `null`, or `undefined`).

**Returns:** `boolean` — `true` if both inputs are deeply equal, `false` otherwise.

**Examples:**

*Compare nested objects*

Recursive structural equality. Returns true when the two values are deeply equal.

```typescript
equalsDeep({ a: { b: 1 } }, { a: { b: 1 } })
// => true
```

*Detect deep differences*

Returns false when nested values differ.

```typescript
equalsDeep({ a: { b: 1 } }, { a: { b: 2 } })
// => false
```

---

### `equalsShallow`

One-level (shallow) object equality.

Two objects are equal when they share the exact same set of own
enumerable string keys and each pair of values satisfies strict equality
(`===`). No recursion: nested objects/arrays are compared by reference.

Falls back to strict equality when either input is `null`, `undefined`
or not an object \u2014 so primitives match if and only if they are `===`.
Arrays are not supported; they always return `false` (unless identical
references). Use `array/equalsShallow` instead.

For recursive structural comparison use equalsDeep. For a diff
structure use diff.

```typescript
import { equalsShallow } from '@helpers4/object';

equalsShallow(objA: unknown, objB: unknown): boolean
```

**Parameters:**

- `objA: unknown` — First value to compare
- `objB: unknown` — Second value to compare

**Returns:** `boolean` — `true` if values are shallowly equal, `false` otherwise.

**Examples:**

*Compare two equal objects*

Uses JSON.stringify for a fast comparison.

```typescript
equalsShallow({ a: 1, b: 2 }, { a: 1, b: 2 })
// => true
```

---

### `flatten`

Flattens a nested object into a single-level object whose keys are the
dot-notation path to each leaf value. The inverse of unflatten.

Only **plain objects** are recursed into — arrays, `Date`, `Map`, `RegExp`,
class instances, and empty plain objects `{}` are kept as opaque leaf
values. This keeps `flatten`/`unflatten` a clean, invertible pair: arrays
can't be losslessly told apart from plain objects once reduced to dotted
keys, so this implementation doesn't attempt it.

Caveat shared by every dotted-path flattening scheme: a key that itself
contains a literal `.` is indistinguishable from real nesting once
flattened (`{ 'a.b': 1 }` and `{ a: { b: 1 } }` both produce `{ 'a.b': 1 }`).

```typescript
import { flatten } from '@helpers4/object';

flatten(obj: Record<string, unknown>): Record<string, unknown>
```

**Parameters:**

- `obj: Record<string, unknown>` — The object to flatten

**Returns:** `Record<string, unknown>` — A single-level object with dot-notation keys

**Examples:**

*Flatten a nested config object*

Each key in the result is the full dot-notation path to a leaf value.

```typescript
flatten({ server: { host: 'localhost', port: 8080 } })
// => { 'server.host': 'localhost', 'server.port': 8080 }
```

*Arrays and special objects are kept as leaves*

Only plain objects are recursed into — arrays stay intact.

```typescript
flatten({ tags: ['a', 'b'], meta: { owner: 'x' } })
// => { tags: ['a', 'b'], 'meta.owner': 'x' }
```

---

### `get`

Gets a value from an object using a dot/bracket-notated path or explicit key array.

**Two path forms are supported:**

1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)
   are both accepted and mixed freely.
   - Dot segments are always string keys: `'a.1'` → key `'1'` (string).
   - Bracket segments are always number keys: `'a[1]'` → key `1` (number).
   - String literal paths give **full compile-time type inference** on the return type.
   - Dynamic (non-literal) strings return `unknown`.

2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
   no parsing performed. Enables symbol-keyed traversal and compile-time type inference:
   `get(obj, ['a', 'b'] as const)` infers the return type from the path.

Both forms support **all objects** (plain objects, arrays, class instances).
Symbol keys are only reachable via the key-array form.

```typescript
import { get } from '@helpers4/object';

get<D>(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D
```

**Parameters:**

- `obj: null | undefined` — The object to read from
- `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
- `defaultValue: D` — Returned when the path is absent or resolves to `undefined`

**Returns:** `D` — The value at the path, or `defaultValue`

```typescript
import { get } from '@helpers4/object';

get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined
```

**Parameters:**

- `obj: null | undefined` — The object to read from
- `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`

**Returns:** `undefined` — The value at the path, or `defaultValue`

```typescript
import { get } from '@helpers4/object';

get<T extends object, P extends string | readonly PropertyKey[]>(obj: T | null | undefined, path: P, defaultValue?: DeepGet<T, ParsePath<P>>): DeepGet<T, ParsePath<P>> | undefined
```

**Parameters:**

- `obj: T | null | undefined` — The object to read from
- `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
- `defaultValue?: DeepGet<T, ParsePath<P>>` — Returned when the path is absent or resolves to `undefined`

**Returns:** `DeepGet<T, ParsePath<P>> | undefined` — The value at the path, or `defaultValue`

**Examples:**

*Access a nested property*

Uses a dot-notated path to retrieve a deeply nested value.

```typescript
get({ a: { b: { c: 42 } } }, 'a.b.c')
// => 42
```

*Return default for missing path*

Returns the default value when the path does not exist.

```typescript
get({ a: 1 }, 'b.c', 'default')
// => 'default'
```

*Get via key array (supports symbols)*

Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys.

```typescript
const id = Symbol('id')
get({ [id]: 'alice' }, [id])
// => 'alice'
```

---

### `groupBy`

Groups an array of items by a key derived from each item.

A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
older targets and provides stricter return-type inference.
`null` and `undefined` are treated as empty arrays and return `{}`.
Items whose computed key is a prototype-polluting string (`__proto__`,
`constructor`, `prototype`) are silently skipped.

```typescript
import { groupBy } from '@helpers4/object';

groupBy<T, K extends PropertyKey>(items: readonly T[] | null | undefined, keyFn: function): Partial<Record<K, T[]>>
```

**Parameters:**

- `items: readonly T[] | null | undefined` — The array to group
- `keyFn: function` — Function that returns the group key for each item

**Returns:** `Partial<Record<K, T[]>>` — A record mapping each key to the array of items with that key

**Examples:**

*Group numbers by parity*

Groups elements by the string key returned by the callback.

```typescript
groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd')
// => { odd: [1, 3], even: [2, 4] }
```

*Group objects by a property*

Use a property accessor as the grouping key.

```typescript
const users = [
  { name: 'Alice', role: 'admin' },
  { name: 'Bob',   role: 'user'  },
  { name: 'Carol', role: 'admin' },
];
groupBy(users, u => u.role)
// => { admin: [{...Alice}, {...Carol}], user: [{...Bob}] }
```

---

### `invert`

Returns a new object with keys and values swapped.
If multiple keys share the same value, the last one wins.
`null` and `undefined` are treated as empty objects and return `{}`.
Entries whose source key **or** value is a prototype-polluting string
(`__proto__`, `constructor`, `prototype`) are silently skipped.

```typescript
import { invert } from '@helpers4/object';

invert<K extends string, V extends PropertyKey>(obj: Record<K, V> | null | undefined): Record<V, K>
```

**Parameters:**

- `obj: Record<K, V> | null | undefined` — The object whose keys and values are to be swapped

**Returns:** `Record<V, K>` — A new object with values as keys and original keys as values

**Examples:**

*Swap keys and values*

Returns a new object with keys and values swapped.

```typescript
invert({ a: 'x', b: 'y', c: 'z' })
// => { x: 'a', y: 'b', z: 'c' }
```

*Build a reverse lookup map*

Useful when you have a code-to-label map and need label-to-code.

```typescript
const STATUS_LABELS = { 200: 'OK', 404: 'Not Found', 500: 'Internal Server Error' };
const LABEL_TO_CODE = invert(STATUS_LABELS);

LABEL_TO_CODE['OK']; // => '200'
```

---

### `isEmpty`

Checks if a plain object has no own enumerable string-keyed properties.
`null` and `undefined` are treated as empty objects and return `true`.

Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
separately if symbol keys matter for your use case.

```typescript
import { isEmpty } from '@helpers4/object';

isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean
```

**Parameters:**

- `value: Record<PropertyKey, unknown> | null | undefined` — The object to check

**Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`

**Examples:**

*Check if an object has no own string-keyed properties*

Returns true for `{}`. Symbol-keyed properties are not counted.

```typescript
isEmpty({})              // => true
isEmpty({ a: 1 })        // => false
isEmpty({ a: undefined }) // => false  (key exists even if value is undefined)
```

*Symbol keys are not counted*

An object with only symbol-keyed properties is considered empty.

```typescript
const sym = Symbol('x');
const obj = { [sym]: 1 };
isEmpty(obj) // => true  (only string keys are counted)
```

---

### `isNonEmpty`

Checks if a plain object has at least one own enumerable string-keyed property.
`null` and `undefined` are treated as empty objects and return `false`.

Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
separately if symbol keys matter for your use case.

```typescript
import { isNonEmpty } from '@helpers4/object';

isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>
```

**Parameters:**

- `value: Record<PropertyKey, unknown> | null | undefined` — The object to check

**Returns:** `value is Record<PropertyKey, unknown>` — `true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`.
Acts as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`.

**Examples:**

*Check if an object has own string-keyed properties*

Returns true when at least one own enumerable string key is present.

```typescript
isNonEmpty({ a: 1 })        // => true
isNonEmpty({ a: undefined }) // => true  (key exists)
isNonEmpty({})              // => false
```

*Guard before iterating object keys*

Use isNonEmpty before looping to avoid processing empty objects.

```typescript
function processConfig(config: Record<string, unknown>): void {
  if (!isNonEmpty(config)) {
    console.warn('Config is empty');
    return;
  }
  for (const key of Object.keys(config)) {
    // process each key
  }
}
```

---

### `kebabCaseKeys`

Recursively transforms every key of a plain object (including keys nested inside arrays and
nested objects) to kebab-case. Handles snake_case, camelCase, PascalCase, and space-separated
keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
untouched, only their position is walked into. An entry whose transformed key is a
prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
same as the rest of `@helpers4/object`.

```typescript
import { kebabCaseKeys } from '@helpers4/object';

kebabCaseKeys<T>(value: T): T
```

**Parameters:**

- `value: T` — The object (or array of objects) to transform

**Returns:** `T` — A new value with every plain-object key converted to kebab-case

**Examples:**

*Convert object keys for a kebab-case config format*

Recursively converts every key, including nested objects, to kebab-case.

```typescript
kebabCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
// => { 'user-name': 'Alice', 'home-address': { 'zip-code': '12345' } }
```

---

### `map`

Transforms the values and/or keys of a plain object in a single pass.

Both callbacks are optional and default to identity (no transformation).
When `mapValue` is omitted the original values are preserved;
when `mapKey` is omitted the original keys are preserved.

Note: if two different keys map to the same output key the last one wins
(insertion order). Entries whose mapped key is a prototype-polluting string
(`__proto__`, `constructor`, `prototype`) are silently skipped.

```typescript
import { map } from '@helpers4/object';

map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: function, mapKey?: function): Record<TKey, TVal>
```

**Parameters:**

- `obj: TObj | null | undefined` — The source object
- `mapValue?: function` — Callback called with `(value, key)` for each entry.
  Defaults to identity.
- `mapKey?: function` — Callback called with `(key, value)` for each entry.
  Defaults to identity.

**Returns:** `Record<TKey, TVal>` — A new object with transformed keys and/or values

**Examples:**

*Transform values*

Maps each value of an object through a transform function.

```typescript
map({ a: 1, b: 2 }, v => v * 10)
// => { a: 10, b: 20 }
```

*Transform keys*

Maps each key of an object through a transform function.

```typescript
map({ a: 1, b: 2 }, undefined, k => k.toUpperCase())
// => { A: 1, B: 2 }
```

*Transform both keys and values in a single pass*

Provide both a value mapper and a key mapper to rewrite the whole object.

```typescript
map(
  { price: 100, discount: 20 },
  v => v / 100,
  k => `${k}Ratio`
)
// => { priceRatio: 1, discountRatio: 0.2 }
```

---

### `mapDeep`

Recursively transforms the keys and/or values of a plain object — the deep counterpart to
map, which only transforms the top level. Descends into nested plain objects and
arrays of them; `Date`, `Map`, `Set`, class instances, and primitives are left untouched
(only their position is walked into, matching cloneDeep's notion of what counts
as a plain-data node worth recursing into).

Both callbacks are optional and default to identity (no transformation). `mapKey` is called
with the *original* key and value (before any recursive value transform); `mapValue` receives
the already-recursed value. Entries whose mapped key is a prototype-polluting string
(`__proto__`, `constructor`, `prototype`) are silently skipped, same as map.

Array elements also go through `mapValue` (with their stringified index as `key`, since
arrays have no property keys of their own) — so values inside a plain array, not just object
properties, get transformed too. `mapKey` is never called for array elements, since there is
no key to rename.

```typescript
import { mapDeep } from '@helpers4/object';

mapDeep<T>(obj: T, mapValue?: function, mapKey?: function): T
```

**Parameters:**

- `obj: T` — The value to transform (typically an object, or an array of objects)
- `mapValue?: function` — Callback called with `(value, key)` for each entry's (already-recursed) value.
  For array elements, `key` is the element's index as a string. Defaults to identity.
- `mapKey?: function` — Callback called with `(key, value)` for each object entry. Defaults to identity.

**Returns:** `T` — A new value with every plain-object key/value transformed

**Examples:**

*Transform every key, recursively*

Unlike map(), mapDeep() walks into nested objects and arrays of objects.

```typescript
mapDeep({ a: { b: 1 } }, undefined, key => key.toUpperCase())
// => { A: { B: 1 } }
```

*Transform every value, recursively*

Values are visited after their own nested content has already been transformed.

```typescript
mapDeep({ a: { b: 1, c: 2 } }, v => typeof v === 'number' ? v * 10 : v)
// => { a: { b: 10, c: 20 } }
```

*Transform values inside arrays too*

Array elements are visited by mapValue as well, keyed by their stringified index.

```typescript
mapDeep({ tags: [1, 2, 3] }, v => typeof v === 'number' ? v * 10 : v)
// => { tags: [10, 20, 30] }
```

---

### `mergeDeep`

Merges two or more objects deeply, returning a **new** object without mutating any input.

Sources are applied left to right. For each key:
- If both sides are plain objects → merged recursively into a new object.
- Otherwise → **later source wins** (the earlier value is overwritten).
- `undefined` in a source **never overwrites** an existing value.
- `null` in a source **does overwrite** and prevents a later source from
  deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`
  → `{ a: { y: 2 } }` — `x: 1` is permanently lost.
- Arrays, class instances, and all non-plain-object values are **replaced**, not merged.

Own enumerable string and symbol keys of each source are processed at the top level;
inherited and non-enumerable properties are skipped. Prototype-polluting keys
(`__proto__`, `constructor`, `prototype`) are silently ignored.
**Note:** symbol keys inside nested plain-object values are not preserved — they are
lost when those values are deep-cloned. Only top-level symbol keys survive.

**TypeScript return type — intersection semantics**

The return type is `A & B & C …` (intersection of all source types). This is accurate
when keys are disjoint or share the same type:
```ts
mergeDeep({ a: 1 }, { b: 'x' })           // { a: number } & { b: string }  ✓
mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } }  ✓
```
When the same key carries **incompatible types** across sources, the intersection
resolves to `never` for that key — TypeScript surfaces the conflict rather than
silently picking a type:
```ts
mergeDeep({ a: 1 }, { a: 'x' })  // { a: never }  ← type conflict detected
```
At runtime the later source always wins (`'x'`), but the `never` type signals that
the caller should align their source types. If intentional, cast the result.

```typescript
import { mergeDeep } from '@helpers4/object';

mergeDeep<T extends [object, rest]>(sources: [rest]): MergeResult<T>
```

**Parameters:**

- `sources: [rest]` — Two or more objects to merge (none are mutated)

**Returns:** `MergeResult<T>` — A new object that is the deep merge of all sources

**Examples:**

*Merge two objects deeply*

Returns a new object — neither input is mutated.

```typescript
mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
// => { a: 1, b: { c: 2, d: 3 }, e: 4 }
```

---

### `omit`

Creates a new object without the specified keys.

```typescript
import { omit } from '@helpers4/object';

omit<T extends Record<string, unknown>, K extends string | number | symbol>(obj: T, keys: readonly K[]): Omit<T, K>
```

**Parameters:**

- `obj: T` — The source object
- `keys: readonly K[]` — The keys to omit

**Returns:** `Omit<T, K>` — A new object without the omitted keys

```typescript
import { omit } from '@helpers4/object';

omit(obj: undefined, keys: readonly string[]): undefined
```

**Parameters:**

- `obj: undefined` — The source object
- `keys: readonly string[]` — The keys to omit

**Returns:** `undefined` — A new object without the omitted keys

```typescript
import { omit } from '@helpers4/object';

omit(obj: null, keys: readonly string[]): null
```

**Parameters:**

- `obj: null` — The source object
- `keys: readonly string[]` — The keys to omit

**Returns:** `null` — A new object without the omitted keys

**Examples:**

*Omit specific keys*

Creates a new object without the specified keys.

```typescript
omit({ a: 1, b: 2, c: 3 }, ['b'])
// => { a: 1, c: 3 }
```

*Remove sensitive fields*

Useful to strip sensitive data before sending to client.

```typescript
const user = { id: 1, name: 'Alice', password: 'secret', token: 'abc123' };
omit(user, ['password', 'token'])
// => { id: 1, name: 'Alice' }
```

---

### `omitBy`

Creates a new object without the own enumerable entries for which
`predicate` returns `true`.

Complements omit for when the keys to remove aren't known ahead of
time — `omit` takes an explicit key list, `omitBy` takes a predicate.

```typescript
import { omitBy } from '@helpers4/object';

omitBy<T extends Record<string, unknown>>(obj: T | null | undefined, predicate: function): Partial<T> | null | undefined
```

**Parameters:**

- `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged.
- `predicate: function` — Called with `(value, key)` for each own enumerable entry

**Returns:** `Partial<T> | null | undefined` — A new object without the matching entries

**Examples:**

*Drop entries matching a predicate*

Unlike omit(), the keys to drop don’t need to be known ahead of time.

```typescript
omitBy({ a: 1, b: undefined, c: 2 }, (value) => value === undefined)
// => { a: 1, c: 2 }
```

*Drop entries by key name*

The predicate also receives the key, so filtering by name works too.

```typescript
omitBy({ id: 1, name: 'x', _internal: true }, (_v, key) => key.startsWith('_'))
// => { id: 1, name: 'x' }
```

---

### `parsePropertyPath`

Parses a dot/bracket-notation property path into an array of string/number
key segments — the same notation accepted by get and set.

- Dot separators (`.`) split segments; each segment becomes a string key.
- Bracket indices (`[n]`) become number keys.
- A leading `.` is treated as "current level" and stripped before parsing,
  so `.[0]` ≡ `[0]` and `.a.b` ≡ `a.b`.
- Empty string (or a bare `.`) returns `['']` (addresses the `''` key on the root object).
- Consecutive dots (`a..b`) produce an empty-string segment: `['a', '', 'b']`.

Results are cached (up to 500 distinct path strings, oldest evicted first)
since real-world callers tend to reuse a small, fixed set of literal paths.

```typescript
import { parsePropertyPath } from '@helpers4/object';

parsePropertyPath(path: string): readonly string | number[]
```

**Parameters:**

- `path: string` — The dot/bracket-notation path string to parse

**Returns:** `readonly string | number[]` — The parsed key segments

**Examples:**

*Parse a dot/bracket-notation path into key segments*

The same notation accepted by get() and set() — bracket indices become numbers.

```typescript
parsePropertyPath('layers[1].name')
// => ['layers', 1, 'name']
```

*Malformed paths throw instead of silently misparsing*

Text trailing a closing bracket within a segment is ambiguous — use 'a[0].b' instead.

```typescript
parsePropertyPath('a[0]b')
// => throws RangeError
```

---

### `pascalCaseKeys`

Recursively transforms every key of a plain object (including keys nested inside arrays and
nested objects) to PascalCase. Handles camelCase, snake_case, kebab-case, and space-separated
keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
untouched, only their position is walked into. An entry whose transformed key is a
prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
same as the rest of `@helpers4/object`.

```typescript
import { pascalCaseKeys } from '@helpers4/object';

pascalCaseKeys<T>(value: T): T
```

**Parameters:**

- `value: T` — The object (or array of objects) to transform

**Returns:** `T` — A new value with every plain-object key converted to PascalCase

**Examples:**

*Convert object keys to PascalCase (e.g. for a C#/JSON.NET consumer)*

Recursively converts every key, including nested objects, to PascalCase.

```typescript
pascalCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
// => { UserName: 'Alice', HomeAddress: { ZipCode: '12345' } }
```

---

### `pick`

Creates a new object with only the specified keys.
Keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are
silently skipped.

```typescript
import { pick } from '@helpers4/object';

pick<T extends Record<string, unknown>, K extends string | number | symbol>(obj: T, keys: readonly K[]): Pick<T, K>
```

**Parameters:**

- `obj: T` — The source object
- `keys: readonly K[]` — The keys to pick

**Returns:** `Pick<T, K>` — A new object with only the picked keys

```typescript
import { pick } from '@helpers4/object';

pick(obj: undefined, keys: readonly string[]): undefined
```

**Parameters:**

- `obj: undefined` — The source object
- `keys: readonly string[]` — The keys to pick

**Returns:** `undefined` — A new object with only the picked keys

```typescript
import { pick } from '@helpers4/object';

pick(obj: null, keys: readonly string[]): null
```

**Parameters:**

- `obj: null` — The source object
- `keys: readonly string[]` — The keys to pick

**Returns:** `null` — A new object with only the picked keys

**Examples:**

*Pick specific keys*

Creates a new object with only the specified keys.

```typescript
pick({ a: 1, b: 2, c: 3 }, ['a', 'c'])
// => { a: 1, c: 3 }
```

*Extract user fields*

Useful to select only the fields you need from an object.

```typescript
const user = { id: 1, name: 'Alice', email: 'alice@example.com', password: 'secret' };
pick(user, ['id', 'name', 'email'])
// => { id: 1, name: 'Alice', email: 'alice@example.com' }
```

---

### `pickBy`

Creates a new object with only the own enumerable entries for which
`predicate` returns `true`.

Complements pick for when the keys to keep aren't known ahead of
time — `pick` takes an explicit key list, `pickBy` takes a predicate.

```typescript
import { pickBy } from '@helpers4/object';

pickBy<T extends Record<string, unknown>>(obj: T | null | undefined, predicate: function): Partial<T> | null | undefined
```

**Parameters:**

- `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged.
- `predicate: function` — Called with `(value, key)` for each own enumerable entry

**Returns:** `Partial<T> | null | undefined` — A new object with only the matching entries

**Examples:**

*Keep only entries matching a predicate*

Unlike pick(), the keys to keep don’t need to be known ahead of time.

```typescript
pickBy({ a: 1, b: 0, c: 2 }, (value) => value > 0)
// => { a: 1, c: 2 }
```

*Keep entries by key name*

The predicate also receives the key, so filtering by name works too.

```typescript
pickBy({ _id: 1, name: 'x', _internal: true }, (_v, key) => !key.startsWith('_'))
// => { name: 'x' }
```

---

### `removeUndefinedNull`

Remove null and undefined values from an object.

```typescript
import { removeUndefinedNull } from '@helpers4/object';

removeUndefinedNull<T extends Record<string, string | number | boolean | null | undefined>>(obj: T): Partial<T>
```

**Parameters:**

- `obj: T` — an object

**Returns:** `Partial<T>` — A shallow copy of the object without null or undefined values

```typescript
import { removeUndefinedNull } from '@helpers4/object';

removeUndefinedNull(obj: null): null
```

**Parameters:**

- `obj: null` — a null object

**Returns:** `null` — null

```typescript
import { removeUndefinedNull } from '@helpers4/object';

removeUndefinedNull(obj: undefined): undefined
```

**Parameters:**

- `obj: undefined` — an undefined object

**Returns:** `undefined` — undefined

**Examples:**

*Strip null and undefined values*

Returns a shallow copy of the object without null or undefined properties.

```typescript
removeUndefinedNull({ a: 1, b: null, c: undefined, d: 'ok' })
// => { a: 1, d: 'ok' }
```

---

### `safeJsonParse`

Parses a JSON string, returning `null` (or a fallback) on any parse failure.

Unlike `JSON.parse`, this never throws. Invalid JSON strings and other
parsing edge-cases resolve to `null` or the provided `fallback`.

```typescript
import { safeJsonParse } from '@helpers4/object';

safeJsonParse<T>(input: string): T | null
```

**Parameters:**

- `input: string` — The JSON string to parse.

**Returns:** `T | null` — The parsed value typed as `T`, or `fallback` on failure.

```typescript
import { safeJsonParse } from '@helpers4/object';

safeJsonParse<T>(input: string, fallback: T): T
```

**Parameters:**

- `input: string` — The JSON string to parse.
- `fallback: T` — Value returned on failure. Defaults to `null` when omitted.

**Returns:** `T` — The parsed value typed as `T`, or `fallback` on failure.

**Examples:**

*Parse valid JSON*

Returns the parsed value when the input is valid JSON.

```typescript
safeJsonParse<{ a: number }>('{"a":1}')
// => { a: 1 }
```

*Return null on invalid input*

Returns null instead of throwing when JSON is malformed.

```typescript
safeJsonParse('invalid')
// => null
```

*Use a fallback value*

Returns the provided fallback when parsing fails.

```typescript
safeJsonParse('invalid', [])
// => []
```

---

### `set`

Sets a value in an object at the given path, creating intermediate objects as needed.

**Two path forms are supported:**

1. **String path** — dot notation and bracket notation, mixed freely.
   - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.
   - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.
   - String literal paths give **full compile-time type inference** on the return type.
   - Dynamic (non-literal) strings return `T` (same object type).

2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
   no parsing performed. Enables symbol-keyed access and precise return-type inference.

Both forms support **all objects** (plain objects, arrays, class instances).
Symbol keys are only reachable via the key-array form.

Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`.
Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype`
is rejected and the original object is returned unchanged (prototype-pollution guard).

```typescript
import { set } from '@helpers4/object';

set<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, value: V): conditional
```

**Parameters:**

- `obj: T` — The object to mutate
- `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
- `value: V` — Value to assign at the path

**Returns:** `conditional` — The mutated object (same reference, narrowed type)

**Examples:**

*Set a nested property (dot notation)*

Creates intermediate objects as needed. All segments are string keys — including numeric-looking ones like "1".

```typescript
set({}, 'a.b.c', 42)
// => { a: { b: { c: 42 } } }

set({}, 'layers.1.name', 'bg')
// => { layers: { '1': { name: 'bg' } } }   // '1' is a string key
```

*Set via bracket notation*

Square-bracket indices become numeric keys. Useful when the path targets an array element.

```typescript
const obj = { layers: [{}, { name: 'old' }] }
set(obj, 'layers[1].name', 'new')
// => { layers: [{}, { name: 'new' }] }
```

*Set via key array (supports symbols)*

Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys.

```typescript
const id = Symbol('id')
set({}, ['user', id], 'alice')
// => { user: { [id]: 'alice' } }
```

---

### `snakeCaseKeys`

Recursively transforms every key of a plain object (including keys nested inside arrays and
nested objects) to snake_case. Handles camelCase, PascalCase, kebab-case, and space-separated
keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
untouched, only their position is walked into. An entry whose transformed key is a
prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
same as the rest of `@helpers4/object`.

```typescript
import { snakeCaseKeys } from '@helpers4/object';

snakeCaseKeys<T>(value: T): T
```

**Parameters:**

- `value: T` — The object (or array of objects) to transform

**Returns:** `T` — A new value with every plain-object key converted to snake_case

**Examples:**

*Convert a camelCase object for a snake_case API*

Recursively converts every key, including nested objects, to snake_case.

```typescript
snakeCaseKeys({ userName: 'Alice', homeAddress: { zipCode: '12345' } })
// => { user_name: 'Alice', home_address: { zip_code: '12345' } }
```

---

### `sortKeys`

Creates a new object with the same entries as the input, but with its own keys sorted.
Shallow only — nested objects are copied as-is, their keys are not re-sorted.
Key order matters for things like `JSON.stringify` diffs and snapshot stability; this does
not change equality (`{a:1,b:2}` and `{b:2,a:1}` are already equal), just iteration/serialization order.
Note: integer-index-like keys (`"0"`, `"1"`, `"42"`...) are always iterated first, in numeric
order, by the JS engine itself — no object key ordering can override that language behavior.
A prototype-polluting key (`__proto__`, `constructor`, `prototype`) is silently skipped,
same as the rest of `@helpers4/object`.

```typescript
import { sortKeys } from '@helpers4/object';

sortKeys<T extends Record<string, unknown>>(obj: T, compareFn?: function): T
```

**Parameters:**

- `obj: T` — The object whose keys to sort
- `compareFn?: function` — Optional custom comparator, passed directly to `Array.prototype.sort`

**Returns:** `T` — A new object with the same entries, own keys in sorted order

**Examples:**

*Sort object keys alphabetically*

Useful for stable JSON.stringify output or predictable snapshot tests.

```typescript
sortKeys({ b: 2, a: 1, c: 3 })
// => { a: 1, b: 2, c: 3 }
```

---

### `titleCaseKeys`

Recursively transforms every key of a plain object (including keys nested inside arrays and
nested objects) to Title Case. Handles camelCase, snake_case, kebab-case, and space-separated
keys. Non-plain-object values — `Date`, `Map`, `Set`, class instances, primitives — are left
untouched, only their position is walked into. An entry whose transformed key is a
prototype-polluting string (`__proto__`, `constructor`, `prototype`) is silently skipped,
same as the rest of `@helpers4/object`.

Note: unlike the other `*CaseKeys` variants, `titleCase` joins words with spaces (e.g.
`'User Name'`) — those keys are only usable via bracket access (`obj['User Name']`), not dot
notation. Useful for display-facing structures (form labels, table headers), not wire formats.

```typescript
import { titleCaseKeys } from '@helpers4/object';

titleCaseKeys<T>(value: T): T
```

**Parameters:**

- `value: T` — The object (or array of objects) to transform

**Returns:** `T` — A new value with every plain-object key converted to Title Case

**Examples:**

*Convert object keys into display-friendly labels*

Recursively converts every key to Title Case — useful for form labels or table headers.

```typescript
titleCaseKeys({ user_name: 'Alice', home_address: { zip_code: '12345' } })
// => { 'User Name': 'Alice', 'Home Address': { 'Zip Code': '12345' } }
```

---

### `unflatten`

Rebuilds a nested object from a single-level object whose keys are
dot-notation paths. The inverse of flatten.

Uses set internally, so intermediate nodes are always created as
plain objects (never arrays — see flatten's doc for why), and any
key segment equal to `__proto__`, `constructor`, or `prototype` is silently
rejected (same prototype-pollution guard as `set`).

```typescript
import { unflatten } from '@helpers4/object';

unflatten(obj: Record<string, unknown>): Record<string, unknown>
```

**Parameters:**

- `obj: Record<string, unknown>` — A single-level object with dot-notation keys

**Returns:** `Record<string, unknown>` — The rebuilt nested object

**Examples:**

*Rebuild a nested object from dotted keys*

The inverse of flatten() — useful for parsing flat config sources (env vars, .ini files).

```typescript
unflatten({ 'server.host': 'localhost', 'server.port': 8080 })
// => { server: { host: 'localhost', port: 8080 } }
```

*Multiple keys under the same parent are merged*

Every dotted key contributes to the same nested structure.

```typescript
unflatten({ 'a.x': 1, 'a.y': 2 })
// => { a: { x: 1, y: 2 } }
```

---

### `unset`

Removes the value at a dot/bracket-notation path or explicit key array,
mutating the object in place. Uses the same path syntax as get/set.

A missing intermediate segment is a no-op (nothing to remove), not an error.
As with `set`, any path containing a string segment equal to `__proto__`,
`constructor`, or `prototype` is rejected and the object is returned unchanged.

The removed key stops appearing in `Object.keys`/`for...in` — unlike setting
it to `undefined`, which would keep the key present.

```typescript
import { unset } from '@helpers4/object';

unset<T extends object>(obj: T, path: string | readonly PropertyKey[]): T
```

**Parameters:**

- `obj: T` — The object to mutate
- `path: string | readonly PropertyKey[]` — Dot/bracket-notation string, or explicit `PropertyKey[]`

**Returns:** `T` — The same object reference, with the key removed if it existed

**Examples:**

*Remove a nested value by path*

Uses the same dot/bracket path syntax as get() and set().

```typescript
const config = { server: { host: 'localhost', debug: true } };
unset(config, 'server.debug')
// => { server: { host: 'localhost' } }
```

*Missing paths are a safe no-op*

Unlike set(), unset() never creates intermediate objects — there is nothing to remove.

```typescript
unset({ a: 1 }, 'x.y.z')
// => { a: 1 }  (unchanged)
```

---

### `update`

Updates the value at a path by applying a function to its current value,
creating intermediate objects as needed. Equivalent to
`set(obj, path, updater(get(obj, path)))` in a single call.

Uses the same path syntax and type-inference rules as get and
set — see those for the full behavior (string vs. `PropertyKey[]`
paths, prototype-pollution guarding, etc.).

```typescript
import { update } from '@helpers4/object';

update<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, updater: function): conditional
```

**Parameters:**

- `obj: T` — The object to mutate
- `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
- `updater: function` — Called with the current value (`undefined` if the path is
  absent); its return value is written back at the path

**Returns:** `conditional` — The mutated object (same reference, narrowed type)

**Examples:**

*Increment a counter in one call*

Equivalent to set(obj, path, updater(get(obj, path))), without repeating the path.

```typescript
const state = { count: 1 };
update(state, 'count', (n) => (n ?? 0) + 1)
// => { count: 2 }
```

*Missing paths create intermediate objects, like set()*

The updater receives undefined when the path does not exist yet.

```typescript
const stats: Record<string, unknown> = {};
update(stats, 'hits.total', (n: number | undefined) => (n ?? 0) + 1)
// => { hits: { total: 1 } }
```

---

## observable

Package: `@helpers4/observable`

### `combine`

Combine two observables with a map function and an optional pre-treatment.

Note: you can use the pre-treatment to add a filter, a distinctUntilChanged,
any other operator that can be used in a pipe, or even an `UntilDestroy`
operator.

```typescript
import { combine } from '@helpers4/observable';

combine<T, U, R>(source1: Observable<T>, source2: Observable<U>, map: function, options?: combineOptions<T, U>): Observable<R>
```

**Parameters:**

- `source1: Observable<T>` — first source of data
- `source2: Observable<U>` — second source of data
- `map: function` — way to combine data
- `options?: combineOptions<T, U>` — options for the combineLatest operator

**Returns:** `Observable<R>` — an observable that emits the result of the map function

**Examples:**

*Combine two observables with a map*

Combines the latest values of two observables using a mapping function.

```typescript
combine(of(1), of(2), ([a, b]) => a + b)
// emits 3
```

---

### `combineLatest`

Combines multiple Observables to create an Observable whose values are
calculated from the latest values of each of its input Observables.

This method relies on combineLatestOperator of rxjs.

The main difference with combineLatestOperator is in case of empty parameters.
If the parameter is empty (empty array or empty object), the result will be
also empty.

ATTENTION: this version doesn't support `scheduler` nor `mapper` as last
argument like in combineLatestOperator.

```typescript
import { combineLatest } from '@helpers4/observable';

combineLatest<A extends readonly unknown[]>(sources: readonly [ObservableInputTuple<A>]): Observable<A>
```

**Parameters:**

- `sources: readonly [ObservableInputTuple<A>]`

```typescript
import { combineLatest } from '@helpers4/observable';

combineLatest<T extends Record<string, ObservableInput<unknown>>>(sourcesObject: T): Observable<mapped>
```

**Parameters:**

- `sourcesObject: T`

**Examples:**

*Combine array of observables*

Combines an array of observables into one that emits arrays of their latest values.

```typescript
combineLatest([of(1), of(2), of(3)])
// emits [1, 2, 3]
```

*Handle empty array*

Returns an observable that emits an empty array when given no sources.

```typescript
combineLatest([])
// emits []
```

---

### `isObservable`

Checks if a value is an RxJS Observable or any compatible observable.

Uses duck-typing: returns `true` for any object with both `.subscribe()` and
`.pipe()` methods, covering `Observable`, `Subject`, `BehaviorSubject`,
`ReplaySubject`, and any RxJS-compatible observable implementation.

```typescript
import { isObservable } from '@helpers4/observable';

isObservable(value: unknown): value is Observable<unknown>
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is Observable<unknown>` — `true` if value is observable-like

**Examples:**

*Detect an RxJS Observable or Subject*

Returns true for Observable, Subject, BehaviorSubject, and any duck-typed observable.

```typescript
import { Observable, Subject } from 'rxjs';
isObservable(new Observable())  // => true
isObservable(new Subject())     // => true
isObservable(Promise.resolve()) // => false
isObservable({})                // => false
```

*Accept either an Observable or a plain value*

Use as a guard to normalize inputs that may be Observables or raw values.

```typescript
import { Observable, of } from 'rxjs';
function toObservable<T>(value: T | Observable<T>): Observable<T> {
  return isObservable(value) ? value : of(value);
}
```

---

## promise

Package: `@helpers4/promise`

### `consoleLogPromise`

Returns a function that logs data to the console and passes it through.

```typescript
import { consoleLogPromise } from '@helpers4/promise';

consoleLogPromise<T>(prefix?: string): function
```

**Parameters:**

- `prefix?: string` — Optional prefix for the console log

**Returns:** `function` — A function that logs and returns the data

**Examples:**

*Log and pass-through in a promise chain*

Creates a function that logs data with an optional prefix and returns it unchanged.

```typescript
Promise.resolve(42).then(consoleLogPromise('value:'))
// logs "value: 42" and resolves with 42
```

---

### `createMutex`

Creates a mutex: a lock allowing at most one holder at a time, queueing excess `acquire()`
callers in FIFO order. A createSemaphore with a single permit.

A factory function rather than a class, matching this package's other stateful helpers
(`debounce`, `throttle`): the returned object closes over an internal semaphore.

Typical use: deduplicating concurrent callers of a non-reentrant operation, e.g. making
sure only one in-flight token-refresh call happens at a time while others wait for it.

```typescript
import { createMutex } from '@helpers4/promise';

createMutex(): Mutex
```

**Returns:** `Mutex` — A Mutex

**Examples:**

*Deduplicate concurrent token refreshes*

Only one caller actually refreshes; the rest wait and reuse its result.

```typescript
const mutex = createMutex();
let cached: string | undefined;

async function getToken() {
  return mutex.run(async () => {
    if (cached) return cached;
    cached = await refreshToken(); // only ever runs once per cache miss
    return cached;
  });
}

await Promise.all([getToken(), getToken(), getToken()]);
// refreshToken() was called exactly once
```

*isLocked() reports whether the lock is currently held*

Useful for diagnostics or conditionally skipping work while locked.

```typescript
const mutex = createMutex();
mutex.isLocked() // => false
const release = await mutex.acquire();
mutex.isLocked() // => true
release();
mutex.isLocked() // => false
```

---

### `createSemaphore`

Creates a semaphore limiting concurrent access to `permits` holders at a time, queueing
excess `acquire()` callers in FIFO order (first to call `acquire` is first granted a
permit when one frees up).

A factory function rather than a class, matching this package's other stateful helpers
(`debounce`, `throttle`): the returned object closes over its internal queue/counter.

Use for concurrency limiting, e.g. rate-limiting calls to an external API. For
mutual-exclusion (at most one holder), see createMutex, which is a semaphore
with one permit.

```typescript
import { createSemaphore } from '@helpers4/promise';

createSemaphore(permits: number): Semaphore
```

**Parameters:**

- `permits: number` — Maximum number of concurrent holders. Must be `>= 1` (`Infinity` is
  accepted, for an effectively uncapped semaphore); a non-integer is floored (`2.9`
  behaves as `2`).

**Returns:** `Semaphore` — A Semaphore

**Examples:**

*Limit concurrent calls to an external API*

At most `permits` calls run at once; the rest queue and wait their turn.

```typescript
const semaphore = createSemaphore(2);
const results = await Promise.all(
  urls.map((url) => semaphore.run(() => fetch(url)))
);
// at most 2 fetch() calls in flight at any time
```

*run() releases the permit even on error*

Prefer run() over manual acquire()/release() — it cannot leak a permit.

```typescript
const semaphore = createSemaphore(1);
await semaphore.run(() => {
  throw new Error('oops');
}).catch(() => {});
semaphore.availablePermits() // => 1, the permit was released
```

*Manual acquire() returns a one-shot release function*

Calling the same release function twice throws — always caught, even under contention.

```typescript
const semaphore = createSemaphore(1);
const release = await semaphore.acquire();
release();
release(); // throws RangeError: this permit was already released
```

---

### `defer`

Runs an async function and guarantees that all deferred callbacks are
executed afterwards, in LIFO order (last registered = first executed),
regardless of whether the main work succeeds or throws.

Inspired by Radashi's `defer`. Useful for resource cleanup, temporary file
removal, or any "undo" logic that must run even on failure.

```typescript
import { defer } from '@helpers4/promise';

defer<T>(fn: function): Promise<T>
```

**Parameters:**

- `fn: function` — An async function that receives a `defer` registration function.

**Returns:** `Promise<T>` — The resolved value of `fn`.

**Examples:**

*Cleanup always runs*

Registered callbacks execute after the main function, even on success.

```typescript
const result = await defer(async (d) => {
  d(() => console.log('cleanup'));
  return 42;
});
// logs: 'cleanup' — result is 42
```

*LIFO order*

Multiple callbacks are called in reverse registration order.

```typescript
await defer(async (d) => {
  d(() => console.log('step 1'));
  d(() => console.log('step 2'));
  d(() => console.log('step 3'));
});
// logs: 'step 3', 'step 2', 'step 1'
```

*Cleanup runs even on failure*

Callbacks still execute when the main function throws; the error is re-thrown after.

```typescript
const releaseLock = () => console.log('lock released');
await defer(async (d) => {
  d(releaseLock);
  throw new Error('something failed');
}).catch(() => {});
// logs: 'lock released'
```

---

### `delay`

Creates a promise that resolves after specified delay

```typescript
import { delay } from '@helpers4/promise';

delay<T = void>(ms: number, value?: T): Promise<T>
```

**Parameters:**

- `ms: number` — Milliseconds to delay
- `value?: T` — Optional value to resolve with

**Returns:** `Promise<T>` — Promise that resolves after delay

**Examples:**

*Wait a specified duration*

Creates a promise that resolves after the given milliseconds.

```typescript
await delay(100)
// resolves after 100ms
```

*Resolve with a value*

Optionally resolves with a provided value.

```typescript
const result = await delay(100, 'done')
// => 'done'
```

---

### `falsyPromiseOrThrow`

Returns a function that passes through falsy data or throws an error.

```typescript
import { falsyPromiseOrThrow } from '@helpers4/promise';

falsyPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is truthy

**Returns:** `function` — A function that returns the data if falsy, or throws

**Examples:**

*Pass through falsy values*

Returns the value if falsy, throws otherwise.

```typescript
Promise.resolve(null).then(falsyPromiseOrThrow('Expected falsy'))
// => null
```

*Throw on truthy values*

Throws an error when the value is truthy.

```typescript
Promise.resolve('oops').then(falsyPromiseOrThrow('Should be empty'))
// throws Error('Should be empty')
```

---

### `guard`

Wraps a function so that if it throws, a default value is returned instead of propagating the error.
Works with both synchronous and asynchronous functions.

```typescript
import { guard } from '@helpers4/promise';

guard<T>(fn: function, defaultValue: T): Promise<T>
```

**Parameters:**

- `fn: function` — The function to guard
- `defaultValue: T` — The value to return if the function throws

**Returns:** `Promise<T>` — The result of the function, or the default value on error

```typescript
import { guard } from '@helpers4/promise';

guard<T>(fn: function, defaultValue: T): T
```

**Parameters:**

- `fn: function` — The function to guard
- `defaultValue: T` — The value to return if the function throws

**Returns:** `T` — The result of the function, or the default value on error

**Examples:**

*Fallback on parse error*

Returns a default value when the function throws.

```typescript
const result = guard(() => JSON.parse('invalid'), {})
// => {}
```

*Pass-through on success*

Returns the function result when it does not throw.

```typescript
const result = guard(() => JSON.parse('{"a":1}'), {})
// => { a: 1 }
```

---

### `meaningPromiseOrThrow`

Returns a function that passes through meaningful data or throws an error.
Data is considered meaningless if it is null, undefined, empty string, empty object, or empty array.

```typescript
import { meaningPromiseOrThrow } from '@helpers4/promise';

meaningPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is meaningless

**Returns:** `function` — A function that returns the data if meaningful, or throws

**Examples:**

*Pass through meaningful values*

Returns the value if it is not empty (null, undefined, empty string, empty object, empty array).

```typescript
Promise.resolve({ key: 'value' }).then(meaningPromiseOrThrow('No data'))
// => { key: 'value' }
```

*Throw on empty values*

Throws when the value is null, undefined, empty string, empty object, or empty array.

```typescript
Promise.resolve({}).then(meaningPromiseOrThrow('Empty!'))
// throws Error('Empty!')
```

---

### `parallel`

Runs an array of async functions with a concurrency limit.
At most `limit` functions will be running at any time. `Infinity` means no cap (every
function starts immediately); any other non-finite or non-positive value (`NaN`, `0`,
negative) is clamped to `1` (fully sequential) rather than rejected.

```typescript
import { parallel } from '@helpers4/promise';

parallel<T>(functions: readonly function[], limit: number): Promise<T[]>
```

**Parameters:**

- `functions: readonly function[]` — Array of functions that return promises
- `limit: number` — Maximum number of concurrent executions

**Returns:** `Promise<T[]>` — Promise that resolves with an array of results in the same order as the input

**Examples:**

*Run tasks with concurrency limit*

Executes async functions with at most N running concurrently.

```typescript
const results = await parallel(
  [() => fetch('/a'), () => fetch('/b'), () => fetch('/c')],
  2
)
// At most 2 requests run at a time; results are in order
```

*Sequential execution with limit of 1*

Setting limit to 1 runs functions one at a time.

```typescript
await parallel([fnA, fnB, fnC], 1)
// Runs fnA, then fnB, then fnC
```

---

### `parallelSettle`

Runs an array of async functions with a concurrency limit, partitioning the outcomes
instead of rejecting on the first failure — parallel with settle's
fulfilled/rejected split.

Takes functions (not already-created promises, unlike settle) so it controls
*when* each one starts: that's what makes the concurrency limit meaningful. At most
`concurrency` functions run at any time; as soon as one settles, the next queued
function starts.

```typescript
import { parallelSettle } from '@helpers4/promise';

parallelSettle<T>(functions: readonly function[], concurrency: number): Promise<object>
```

**Parameters:**

- `functions: readonly function[]` — Array of functions that return promises
- `concurrency: number` — Maximum number of concurrent executions. `Infinity` means no cap; any
  other non-finite or non-positive value (`NaN`, `0`, negative) is clamped to `1` (fully
  sequential) rather than rejected.

**Returns:** `Promise<object>` — An object with `fulfilled` values and `rejected` reasons, each in input order

**Examples:**

*Limit concurrency without one failure stopping the rest*

Runs at most `concurrency` functions at a time and partitions outcomes instead of rejecting on the first failure.

```typescript
const { fulfilled, rejected } = await parallelSettle(
  [() => fetch('/a'), () => fetch('/b'), () => fetch('/c')],
  2
)
// At most 2 requests run at a time; a failing request doesn't stop the others
```

*All functions succeed*

Returns an empty rejected array when nothing fails.

```typescript
const { fulfilled, rejected } = await parallelSettle(
  [() => Promise.resolve('a'), () => Promise.resolve('b')],
  2
)
// => { fulfilled: ['a', 'b'], rejected: [] }
```

---

### `resolveRecord`

Resolves an array of keys into a record by calling an async mapper for each key.
All mapper calls run concurrently via `Promise.all`.

Unlike parallel, which returns an array, `resolveRecord` preserves the
key-to-value relationship in the result.

```typescript
import { resolveRecord } from '@helpers4/promise';

resolveRecord<K extends PropertyKey, V>(keys: readonly K[], mapper: function): Promise<Record<K, V>>
```

**Parameters:**

- `keys: readonly K[]` — The keys to resolve
- `mapper: function` — Async function called for each key, returning the associated value

**Returns:** `Promise<Record<K, V>>` — A record mapping each key to its resolved value

**Examples:**

*Fetch data for multiple keys concurrently*

All mapper calls run in parallel via Promise.all.

```typescript
const stars = await resolveRecord(
  ['helpers4/typescript', 'helpers4/devcontainer'],
  async (repo) => fetchRepoStars(repo)
);
// => { 'helpers4/typescript': 42, 'helpers4/devcontainer': 17 }
```

---

### `retry`

Retries a promise-returning function up to maxAttempts times

```typescript
import { retry } from '@helpers4/promise';

retry<T>(fn: function, maxAttempts: number, delayMs: number): Promise<T>
```

**Parameters:**

- `fn: function` — The function to retry
- `maxAttempts: number` (default: `3`) — Maximum number of attempts
- `delayMs: number` (default: `1000`) — Delay between attempts in milliseconds

**Returns:** `Promise<T>` — Promise that resolves with the result or rejects with the last error

**Examples:**

*Retry a failing function*

Retries the function up to maxAttempts times before giving up.

```typescript
let attempt = 0;
await retry(() => {
  attempt++;
  if (attempt < 3) throw new Error('not yet');
  return Promise.resolve('success');
}, 3, 10)
// => 'success' (after 2 failures)
```

---

### `safeFetch`

Wraps `fetch` with built-in error handling: returns `null` when the
request fails (network error, non-OK status, or parse error) instead
of throwing.

```typescript
import { safeFetch } from '@helpers4/promise';

safeFetch<T>(input: URL | RequestInfo, init?: RequestInit, options: SafeFetchOptions): Promise<T | null>
```

**Parameters:**

- `input: URL | RequestInfo` — URL or `Request` object passed to `fetch`
- `init?: RequestInit` — Optional `RequestInit` options passed to `fetch`
- `options: SafeFetchOptions` (default: `{}`) — Parsing options (default: `{ parse: 'json' }`)

**Returns:** `Promise<T | null>` — The parsed response body, or `null` on any failure

**Examples:**

*Fetch JSON safely*

Returns `null` on network error or non-OK status instead of throwing.

```typescript
const repo = await safeFetch<{ stars: number }>(
  'https://api.github.com/repos/helpers4/typescript'
);
if (repo === null) {
  console.warn('Failed to fetch repo data');
} else {
  console.log(repo.stars);
}
```

*Fetch plain text*

Pass { parse: "text" } to get the raw response body as a string.

```typescript
const content = await safeFetch<string>(
  'https://example.com/data.txt',
  undefined,
  { parse: 'text' }
);
```

---

### `settle`

Runs an array of promises concurrently and partitions the outcomes instead of
rejecting on the first failure, unlike `Promise.all`.
Built on top of `Promise.allSettled`, but returns fulfilled values and rejection
reasons already split apart so callers don't need to inspect `status` themselves.

No concurrency limit, and none is possible here: `promises` are already-constructed
`Promise` objects, and a promise starts running the moment it's created — by the time
`settle` receives the array, every promise in it is already in flight. There is
nothing left to throttle. If you need to cap how many run at once (e.g. many file
reads or requests), use parallelSettle instead, which takes functions
(`() => Promise<T>`) so it controls *when* each one starts.

```typescript
import { settle } from '@helpers4/promise';

settle<T>(promises: readonly Promise<T>[]): Promise<object>
```

**Parameters:**

- `promises: readonly Promise<T>[]` — Promises to run concurrently

**Returns:** `Promise<object>` — An object with `fulfilled` values and `rejected` reasons, each in input order

**Examples:**

*Partition fulfilled and rejected outcomes*

Runs promises concurrently and splits results instead of rejecting on the first failure.

```typescript
const { fulfilled, rejected } = await settle([
  Promise.resolve(1),
  Promise.reject(new Error('boom')),
  Promise.resolve(3),
])
// => { fulfilled: [1, 3], rejected: [Error('boom')] }
```

*All promises succeed*

Returns an empty rejected array when nothing fails.

```typescript
const { fulfilled, rejected } = await settle([Promise.resolve('a'), Promise.resolve('b')])
// => { fulfilled: ['a', 'b'], rejected: [] }
```

---

### `timeout`

Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration.

```typescript
import { timeout } from '@helpers4/promise';

timeout<T>(promise: Promise<T>, ms: number): Promise<T>
```

**Parameters:**

- `promise: Promise<T>` — The promise to wrap
- `ms: number` — Timeout duration in milliseconds

**Returns:** `Promise<T>` — A promise that rejects with `TimeoutError` if the timeout is exceeded

**Examples:**

*Reject a slow promise*

Throws a TimeoutError if the promise does not resolve in time.

```typescript
await timeout(fetch('/api/data'), 5000)
// Rejects with TimeoutError if fetch takes longer than 5s
```

*Resolve fast promise normally*

Returns the value if the promise resolves before the timeout.

```typescript
const result = await timeout(Promise.resolve('fast'), 1000)
// => 'fast'
```

---

### `truthyPromiseOrThrow`

Returns a function that passes through truthy data or throws an error.

```typescript
import { truthyPromiseOrThrow } from '@helpers4/promise';

truthyPromiseOrThrow<T>(error: string): function
```

**Parameters:**

- `error: string` — The error message to throw if data is falsy

**Returns:** `function` — A function that returns the data if truthy, or throws

**Examples:**

*Pass through truthy values*

Returns the value if truthy, throws otherwise.

```typescript
Promise.resolve('data').then(truthyPromiseOrThrow('No data'))
// => 'data'
```

*Throw on falsy values*

Throws an error when the value is falsy.

```typescript
Promise.resolve('').then(truthyPromiseOrThrow('Empty!'))
// throws Error('Empty!')
```

---

### `tryit`

Wraps a function so it never throws. Instead, it returns a `[error, result]` tuple.
Useful for avoiding try/catch blocks and handling errors in a functional style.

```typescript
import { tryit } from '@helpers4/promise';

tryit<TArgs extends readonly unknown[], TReturn>(fn: function): function
```

**Parameters:**

- `fn: function` — The function to wrap (sync or async)

**Returns:** `function` — A new function that returns a `Result` tuple

**Examples:**

*Safe JSON parsing*

Wraps JSON.parse to return a tuple instead of throwing.

```typescript
const safeParse = tryit(JSON.parse);
const [error, data] = safeParse('{"a":1}');
// error === undefined, data === { a: 1 }
```

*Catching errors without try/catch*

On error, the first element of the tuple is the Error.

```typescript
const safeParse = tryit(JSON.parse);
const [error, data] = safeParse('invalid');
// error instanceof SyntaxError, data === undefined
```

---

## set

Package: `@helpers4/set`

### `countBy`

Groups the values of a Set by a derived key and counts how many fall into each group.

```typescript
import { countBy } from '@helpers4/set';

countBy<T, R>(set: ReadonlySet<T>, fn: function): Map<R, number>
```

**Parameters:**

- `set: ReadonlySet<T>` — The Set to summarize
- `fn: function` — Function deriving the grouping key from each value

**Returns:** `Map<R, number>` — A Map from derived key to the number of values in that group

**Examples:**

*Count values by parity*

Groups values by a derived key and counts how many fall into each group.

```typescript
countBy(new Set([1, 2, 3, 4]), v => v % 2 === 0 ? 'even' : 'odd')
// => Map(2) { 'odd' => 2, 'even' => 2 }
```

---

### `filter`

Creates a new Set containing only the values for which the predicate returns true.

```typescript
import { filter } from '@helpers4/set';

filter<T>(set: ReadonlySet<T>, predicate: function): Set<T>
```

**Parameters:**

- `set: ReadonlySet<T>` — The Set to filter
- `predicate: function` — Function invoked with each value — return true to keep it

**Returns:** `Set<T>` — A new Set with only the matching values

**Examples:**

*Keep only even values*

Creates a new Set with only the values that satisfy the predicate.

```typescript
filter(new Set([1, 2, 3, 4]), value => value % 2 === 0)
// => Set(2) { 2, 4 }
```

---

### `map`

Creates a new Set with each value transformed by a function. If two values transform to the
same result, duplicates collapse — the returned Set may be smaller than the input.

```typescript
import { map } from '@helpers4/set';

map<T, R>(set: ReadonlySet<T>, fn: function): Set<R>
```

**Parameters:**

- `set: ReadonlySet<T>` — The Set to transform
- `fn: function` — Function invoked with each value, returning the new value

**Returns:** `Set<R>` — A new Set of transformed values

**Examples:**

*Transform every value*

Creates a new Set with each value transformed by a function.

```typescript
map(new Set([1, 2, 3]), value => value * 10)
// => Set(3) { 10, 20, 30 }
```

*Duplicates collapse*

If two values transform to the same result, the Set naturally deduplicates.

```typescript
map(new Set([1, 2, 3]), () => 'same')
// => Set(1) { 'same' }
```

---

### `toMapByKey`

Builds a Map from a Set, keyed by a derived key. When two values derive the same key,
the later value (in iteration order) wins (last-write-wins, matching `Map.prototype.set`).

Named `toMapByKey`, not `keyBy` (lodash's name for the same idea) — lodash's `_.keyBy`
returns a plain object, not a `Map`; the `to<Type>` prefix (matching `toSorted`/`toReversed`)
makes the actual return type unambiguous.

```typescript
import { toMapByKey } from '@helpers4/set';

toMapByKey<T, K>(set: ReadonlySet<T>, fn: function): Map<K, T>
```

**Parameters:**

- `set: ReadonlySet<T>` — The Set to index
- `fn: function` — Function deriving the key for each value

**Returns:** `Map<K, T>` — A Map from derived key to value

**Examples:**

*Turn a Set into a lookup Map*

Builds a Map keyed by a derived value, for O(1) lookup by that key.

```typescript
toMapByKey(new Set([{ id: 'a' }, { id: 'b' }]), item => item.id)
// => Map(2) { 'a' => {...}, 'b' => {...} }
```

---

## string

Package: `@helpers4/string`

### `camelCase`

Converts a string to camelCase.
Handles PascalCase, kebab-case, snake_case, spaces, and mixed formats.

An embedded run of capitals is treated as an acronym boundary: only its last letter starts
the next word, the rest are lowercased (matching lodash's `camelCase` convention) — so an
already-camelCase identifier containing an acronym is not left untouched.

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in camelCase

**Examples:**

*Convert kebab-case to camelCase*

Converts a kebab-case string to camelCase.

```typescript
camelCase('my-component-name')
// => 'myComponentName'
```

---

### `capitalize`

Capitalizes the first letter of a string.
By default, lowercases the remaining characters.
Pass `{ lowercaseRest: false }` to only uppercase the first character.

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: string, options?: CapitalizeOptions): string
```

**Parameters:**

- `str: string` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `string` — String with first letter uppercased

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: undefined, options?: CapitalizeOptions): undefined
```

**Parameters:**

- `str: undefined` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `undefined` — String with first letter uppercased

```typescript
import { capitalize } from '@helpers4/string';

capitalize(str: null, options?: CapitalizeOptions): null
```

**Parameters:**

- `str: null` — The string to capitalize
- `options?: CapitalizeOptions` — Options

**Returns:** `null` — String with first letter uppercased

**Examples:**

*Capitalize a word*

Uppercases the first letter and lowercases the rest.

```typescript
capitalize('hello')
// => 'Hello'
```

*Handle mixed case*

Lowercases all letters except the first one (default behaviour).

```typescript
capitalize('hELLO')
// => 'Hello'
```

*Uppercase first only — leave rest untouched*

Use { lowercaseRest: false } to preserve the original casing of the remaining characters.

```typescript
capitalize('hELLO', { lowercaseRest: false })
// => 'HELLO'
```

---

### `dedent`

Strips the common leading whitespace from every line of a multi-line
string, and trims a single leading/trailing blank line if present.

Lets you write readable, indented multi-line strings in source code
(typically template literals) without that indentation leaking into
the output.

```typescript
import { dedent } from '@helpers4/string';

dedent(str: string): string
```

**Parameters:**

- `str: string` — The string to dedent

**Returns:** `string` — The dedented string

**Examples:**

*Write readable multi-line strings without leaking source indentation*

Strips the common leading whitespace and the wrapping blank lines.

```typescript
dedent(`
  Hello
    World
`)
// => 'Hello\n  World'
```

*The minimum indentation across all lines is what gets removed*

Lines with more indentation than the minimum keep their relative indent.

```typescript
dedent('    a\n  b\n      c')
// => '  a\nb\n    c'
```

---

### `escapeHtml`

Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.

Use this to safely embed untrusted content into HTML attribute values or
text nodes without risk of XSS injection.

```typescript
import { escapeHtml } from '@helpers4/string';

escapeHtml(str: string): string
```

**Parameters:**

- `str: string` — The string to escape.

**Returns:** `string` — The escaped string.

**Examples:**

*Escape script tags*

Converts < > " ' & to their HTML entities to prevent XSS.

```typescript
escapeHtml('<script>alert("xss")</script>')
// => '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
```

*Safe interpolation in templates*

Safe for inserting untrusted strings into HTML.

```typescript
const userInput = '<b>bold</b>';
escapeHtml(userInput) // => '&lt;b&gt;bold&lt;/b&gt;'
```

---

### `escapeRegExp`

Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`)
in a string so it can be safely embedded in a `RegExp` pattern.

Use this before building a `RegExp` from untrusted or dynamic input — without
it, characters like `.` or `(` change the pattern's meaning instead of being
matched literally.

```typescript
import { escapeRegExp } from '@helpers4/string';

escapeRegExp(str: string): string
```

**Parameters:**

- `str: string` — The string to escape

**Returns:** `string` — The escaped string, safe to embed in a `RegExp` pattern

**Examples:**

*Escape metacharacters before building a RegExp*

Without escaping, "." and "?" would change the pattern's meaning.

```typescript
escapeRegExp('1 + 1 = 2?')
// => '1 \\+ 1 = 2\\?'
```

*Safely search for untrusted user input*

Escaping lets user-provided text be matched literally instead of as a pattern.

```typescript
const userInput = 'a.b';
new RegExp(escapeRegExp(userInput)).test('a.b')   // => true
new RegExp(escapeRegExp(userInput)).test('axb')   // => false (literal '.', not "any char")
```

---

### `extractErrorMessage`

Convert an error to a readable message.

```typescript
import { extractErrorMessage } from '@helpers4/string';

extractErrorMessage(error: unknown, stringify: string | true): string
```

**Parameters:**

- `error: unknown` — an error
- `stringify: string | true` — stringifies the error if no extractable message is found

**Returns:** `string` — a readable message or a stringified error if stringify is true, otherwise undefined

```typescript
import { extractErrorMessage } from '@helpers4/string';

extractErrorMessage(error?: unknown, stringify?: string | boolean): string | undefined
```

**Parameters:**

- `error?: unknown` — an error
- `stringify?: string | boolean` — stringifies the error if no extractable message is found

**Returns:** `string | undefined` — a readable message or a stringified error if stringify is true, otherwise undefined

**Examples:**

*Extract message from Error object*

Returns the stringified Error, including the class prefix.

```typescript
extractErrorMessage(new Error('Something went wrong'))
// => 'Error: Something went wrong'
```

*Handle string errors*

Returns the string directly when the error is a plain string.

```typescript
extractErrorMessage('plain error')
// => 'plain error'
```

*Stringify unknown errors*

When stringify is true, falls back to JSON.stringify for unrecognized errors.

```typescript
extractErrorMessage(42, true)
// => '42'
```

---

### `formatProgressBar`

Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width`
cells proportional to `value / max`.

`value` is clamped to `[0, max]` before computing the ratio — out-of-range values (negative,
above `max`) produce an empty or fully-filled bar instead of throwing. Non-finite `max`
(`NaN`, `Infinity`) is treated as `0`, yielding an empty bar.

```typescript
import { formatProgressBar } from '@helpers4/string';

formatProgressBar(value: number, options: ProgressBarOptions): string
```

**Parameters:**

- `value: number` — The current value
- `options: ProgressBarOptions` (default: `{}`) — Bar rendering options

**Returns:** `string` — A string of `width` repeated filled/empty characters

**Examples:**

*Default 20-cell bar*

Renders a percentage as a filled/empty block bar using the default width and characters.

```typescript
formatProgressBar(65)
// => '▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░'
```

*Custom width, characters, and scale*

Renders a value against a custom max, width, and fill characters — useful for anything scored out of a different total than 100.

```typescript
formatProgressBar(3, { width: 10, max: 5, filledChar: '#', emptyChar: '-' })
// => '######----'
```

---

### `injectWordBreaks`

Adds word-break opportunities to a string so it can wrap cleanly in narrow
UI containers such as side panels or table cells.

Invisible zero-width spaces (`\u200B`) are inserted at meaningful
boundaries — camelCase splits, path separators, token edges — while
protected spans (URLs, emails, HTML) and atomic numeric values (`-0.1%`,
`12ms`, `1e-3`) are never broken. The visible text content is unchanged.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: string): string
```

**Parameters:**

- `str: string` — The string to process.

**Returns:** `string` — The string with word-break opportunities injected at meaningful
boundaries.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to process.

**Returns:** `undefined` — The string with word-break opportunities injected at meaningful
boundaries.

```typescript
import { injectWordBreaks } from '@helpers4/string';

injectWordBreaks(str: null): null
```

**Parameters:**

- `str: null` — The string to process.

**Returns:** `null` — The string with word-break opportunities injected at meaningful
boundaries.

**Examples:**

*camelCase identifier*

Inserts ZWS at each camelCase boundary so a long identifier can wrap in a narrow column.

```typescript
injectWordBreaks('getUserProfileData')
// => 'get\u200BUser\u200BProfile\u200BData'
```

*Comma-separated tokens*

The comma attaches to the left token so a line never starts with a comma.

```typescript
injectWordBreaks('foo,bar')
// => 'foo,\u200Bbar'
```

*Atomic numeric value*

Signed decimals and numbers with units (-0.1%, 12ms, -2.4E+6) are never split.

```typescript
injectWordBreaks('-0.1%')
// => '-0.1%'   (unchanged — atomic value)
```

*File path*

Slashes become wrap points so a long path can break at each component.

```typescript
injectWordBreaks('path/to/my_file')
// => 'path\u200B/\u200Bto\u200B/\u200Bmy_file'
```

*URL is preserved intact*

URLs are D0-protected spans — no ZWS is inserted inside or adjacent to them.

```typescript
injectWordBreaks('https://example.com/foo/bar')
// => 'https://example.com/foo/bar'   (unchanged)
```

---

### `isBlank`

Checks if a string is blank — empty or contains only whitespace characters.
`null` and `undefined` are considered blank and return `true`.

Uses `String.prototype.trim()` internally, which covers all ECMAScript
whitespace: standard ASCII whitespace (`\t`, `\n`, `\r`, `\f`, `\v`),
non-breaking space (U+00A0), BOM (U+FEFF), and all Unicode "Space_Separator"
category characters (en space, em space, thin space, ideographic space, etc.).

**Zero-width characters** (U+200B zero-width space, U+200C, U+200D, U+2060)
are **not** treated as whitespace — they are Unicode "Format" (Cf) characters,
not spaces. Strip them explicitly if needed:
`isBlank(value.replace(/[​-‍⁠]/g, ''))`

```typescript
import { isBlank } from '@helpers4/string';

isBlank(value: string | null | undefined): boolean
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `boolean` — `true` if the string is empty, contains only whitespace, or is `null`/`undefined`

**Examples:**

*Detect empty or whitespace-only strings*

Returns true for "" and for any string made entirely of whitespace — including non-breaking space (U+00A0), en/em spaces, ideographic space, and BOM.

```typescript
isBlank('')      // => true
isBlank('   ')   // => true
isBlank('\t\n')  // => true
isBlank(' ')     // => true   (non-breaking space U+00A0)
isBlank('foo')   // => false
isBlank(' x ')   // => false
```

*Form validation — reject blank input*

Use isBlank to reject fields that contain only whitespace.

```typescript
function validateName(name: string): string | null {
  if (isBlank(name)) return 'Name is required';
  return null;
}
validateName('')    // => 'Name is required'
validateName('   ') // => 'Name is required'
validateName('Ada') // => null
```

---

### `isEmpty`

Checks if a string is empty (`""`), `null`, or `undefined`.

This is a strict emptiness check — whitespace-only strings are **not** considered
empty. Use `isEmpty(value?.trim())` if you need to treat blank strings as empty.

```typescript
import { isEmpty } from '@helpers4/string';

isEmpty(value: string | null | undefined): value is "" | null | undefined
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `value is "" | null | undefined` — `true` if the string is `""`, `null`, or `undefined`

**Examples:**

*Check if a string is empty*

Returns true only for `""`. Whitespace-only strings are not considered empty.

```typescript
isEmpty('')    // => true
isEmpty(' ')   // => false  (whitespace is content)
isEmpty('foo') // => false
```

*Treat blank strings as empty by trimming first*

Compose with .trim() when whitespace-only should also be considered empty.

```typescript
isEmpty(''.trim())   // => true
isEmpty('   '.trim()) // => true
isEmpty('hi'.trim())  // => false
```

---

### `isNonEmpty`

Checks if a string is non-empty (has at least one character).
`null` and `undefined` are considered empty and return `false`.

Whitespace-only strings are considered non-empty.
Use `isNonEmpty(value?.trim())` if you need to exclude blank strings.

```typescript
import { isNonEmpty } from '@helpers4/string';

isNonEmpty(value: string | null | undefined): value is string
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `value is string` — `true` if the string has at least one character; `false` for `""`, `null`, or `undefined`.
Acts as a type guard: the `if` branch narrows `string | null | undefined` to `string`.

**Examples:**

*Check if a string has content*

Returns true for any string with at least one character, including whitespace.

```typescript
isNonEmpty('hello') // => true
isNonEmpty(' ')     // => true  (whitespace is content)
isNonEmpty('')      // => false
```

*Exclude blank strings by trimming first*

Compose with .trim() when whitespace-only strings should be treated as empty.

```typescript
isNonEmpty('hello'.trim()) // => true
isNonEmpty('   '.trim())   // => false
isNonEmpty(''.trim())      // => false
```

---

### `isNotBlank`

Checks if a string is not blank — non-empty and contains at least one
non-whitespace character.
`null` and `undefined` are considered blank and return `false`.

Uses `String.prototype.trim()` internally. See `isBlank` for the full list
of characters considered whitespace (includes non-breaking space, en/em space,
ideographic space, etc.).

```typescript
import { isNotBlank } from '@helpers4/string';

isNotBlank(value: string | null | undefined): boolean
```

**Parameters:**

- `value: string | null | undefined` — The string to check

**Returns:** `boolean` — `true` if the string has at least one non-whitespace character; `false` for blank, `null`, or `undefined`

**Examples:**

*Check that a string has real content*

Returns true only when the string contains at least one non-whitespace character.

```typescript
isNotBlank('foo')  // => true
isNotBlank(' x ')  // => true
isNotBlank('')     // => false
isNotBlank('   ')  // => false
isNotBlank('\t')   // => false
```

*Filter out blank strings from an array*

Use as a predicate in .filter() to keep only strings with real content.

```typescript
const tags = ['typescript', '  ', '', 'helpers'];
tags.filter(isNotBlank)
// => ['typescript', 'helpers']
```

---

### `kebabCase`

Converts a string to kebab-case.
Handles camelCase, PascalCase, snake_case, spaces, and mixed formats.

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in kebab-case

**Examples:**

*Convert camelCase to kebab-case*

Converts a camelCase string to kebab-case.

```typescript
kebabCase('myComponentName')
// => 'my-component-name'
```

---

### `leadingSentence`

Extracts the leading sentence from a string.
`null` and `undefined` are passed through unchanged.

A sentence boundary is detected at the first occurrence of `.`, `?`, `!`,
`…`, or `;` followed by whitespace or end of string. Newlines are collapsed
to spaces before matching.

If no boundary is found the entire (cleaned) string is returned.

To cap the result at a maximum length, combine with truncate:
```ts
truncate(leadingSentence(input), 120)
```

```typescript
import { leadingSentence } from '@helpers4/string';

leadingSentence(input: string): string
```

**Parameters:**

- `input: string` — The source string

**Returns:** `string` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

```typescript
import { leadingSentence } from '@helpers4/string';

leadingSentence(input: null): null
```

**Parameters:**

- `input: null` — The source string

**Returns:** `null` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

```typescript
import { leadingSentence } from '@helpers4/string';

leadingSentence(input: undefined): undefined
```

**Parameters:**

- `input: undefined` — The source string

**Returns:** `undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

```typescript
import { leadingSentence } from '@helpers4/string';

leadingSentence(input: string | null | undefined): string | null | undefined
```

**Parameters:**

- `input: string | null | undefined` — The source string

**Returns:** `string | null | undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish

**Examples:**

*Extract the leading sentence*

Returns the first sentence, terminated by . ? or !.

```typescript
leadingSentence('Returns the sum of an array. Works with any numbers.')
// => 'Returns the sum of an array.'
```

*Works with ? and !*

Recognises question marks and exclamation marks as sentence terminators.

```typescript
leadingSentence('Is it done? Yes it is!')
// => 'Is it done?'
```

*Cap length by combining with truncate*

Use truncate to limit the result to a fixed number of characters.

```typescript
truncate(leadingSentence(input), 120)
```

---

### `pascalCase`

Converts a string to PascalCase.
Handles camelCase, kebab-case, snake_case, spaces, and mixed formats.

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in PascalCase

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in PascalCase

```typescript
import { pascalCase } from '@helpers4/string';

pascalCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in PascalCase

**Examples:**

*Convert kebab-case to PascalCase*

Converts a kebab-case string to PascalCase.

```typescript
pascalCase('my-component')
// => 'MyComponent'
```

*Convert snake_case to PascalCase*

Also handles snake_case and other formats.

```typescript
pascalCase('user_first_name')
// => 'UserFirstName'
```

---

### `removeDiacritics`

Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`.

Works by Unicode-decomposing each character into its base letter plus
combining marks (`'é'` → `'e'` + a combining acute accent), then
stripping the marks. Same technique already used internally by `slugify`.

```typescript
import { removeDiacritics } from '@helpers4/string';

removeDiacritics(str: string): string
```

**Parameters:**

- `str: string` — The string to strip diacritics from

**Returns:** `string` — The string with diacritics removed

**Examples:**

*Strip accents from a string*

Useful for accent-insensitive search or generating ASCII-safe identifiers.

```typescript
removeDiacritics('café')
// => 'cafe'
```

*Plain ASCII text is left unchanged*

Only characters with combining diacritical marks are affected.

```typescript
removeDiacritics('hello world')
// => 'hello world'
```

---

### `slugify`

Converts a string into a URL-friendly slug.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: string): string
```

**Parameters:**

- `str: string` — The string to convert into a slug.

**Returns:** `string` — A lowercase, hyphen-separated slug safe for URLs.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert into a slug.

**Returns:** `undefined` — A lowercase, hyphen-separated slug safe for URLs.

```typescript
import { slugify } from '@helpers4/string';

slugify(str: null): null
```

**Parameters:**

- `str: null` — The string to convert into a slug.

**Returns:** `null` — A lowercase, hyphen-separated slug safe for URLs.

**Examples:**

*Create a URL-safe slug*

Converts a string into a lowercase, hyphen-separated slug.

```typescript
slugify('Hello World!')
// => 'hello-world'
```

*Handle accented characters*

Normalizes Unicode characters and strips diacritics.

```typescript
slugify('Crème brûlée')
// => 'creme-brulee'
```

---

### `snakeCase`

Converts a string to snake_case.
Handles camelCase, PascalCase, kebab-case, spaces, and mixed formats.

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in snake_case

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in snake_case

```typescript
import { snakeCase } from '@helpers4/string';

snakeCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in snake_case

**Examples:**

*Convert camelCase to snake_case*

Converts a camelCase string to snake_case.

```typescript
snakeCase('myVariableName')
// => 'my_variable_name'
```

*Convert kebab-case to snake_case*

Also handles kebab-case and other formats.

```typescript
snakeCase('my-component-name')
// => 'my_component_name'
```

---

### `template`

Interpolates `{{key}}` placeholders in a template string with values from
a data record. Unknown keys are replaced with an empty string.

No `eval` or `Function` constructor is used — substitution is purely
regex-based. Nested expressions and logic are intentionally out of scope.

```typescript
import { template } from '@helpers4/string';

template(str: string, data: Record<string, unknown>): string
```

**Parameters:**

- `str: string` — The template string containing `{{key}}` placeholders.
- `data: Record<string, unknown>` — A record mapping placeholder names to replacement values.

**Returns:** `string` — The template string with all placeholders replaced.

**Examples:**

*Simple interpolation*

Replaces {{key}} placeholders with values from the data object.

```typescript
template('Hello, {{name}}!', { name: 'Alice' })
// => 'Hello, Alice!'
```

*Multiple placeholders*

All matching placeholders are replaced in a single pass.

```typescript
template('{{greeting}}, {{name}}!', { greeting: 'Hi', name: 'Bob' })
// => 'Hi, Bob!'
```

*Missing keys become empty string*

Unknown placeholders are replaced with an empty string.

```typescript
template('Hello, {{name}}!', {})
// => 'Hello, !'
```

---

### `titleCase`

Converts a string to Title Case.
Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and mixed formats.

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: string): string
```

**Parameters:**

- `str: string` — The string to convert

**Returns:** `string` — String in Title Case

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The string to convert

**Returns:** `undefined` — String in Title Case

```typescript
import { titleCase } from '@helpers4/string';

titleCase(str: null): null
```

**Parameters:**

- `str: null` — The string to convert

**Returns:** `null` — String in Title Case

**Examples:**

*Convert kebab-case to Title Case*

Transforms a delimited string into Title Case.

```typescript
titleCase('my-component-name')
// => 'My Component Name'
```

*Convert camelCase to Title Case*

Also handles camelCase by splitting on uppercase transitions.

```typescript
titleCase('queryItems')
// => 'Query Items'
```

---

### `truncate`

Truncates a string to `maxLength` characters, appending an ellipsis when cut.

The ellipsis counts toward `maxLength`, so the result is always at most
`maxLength` characters long. If the string is already within the limit, it
is returned unchanged (no ellipsis appended). `null` and `undefined` inputs
are returned as-is to align with other string helpers.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: undefined, maxLength: number, ellipsis?: string): undefined
```

**Parameters:**

- `input: undefined` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `undefined` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: null, maxLength: number, ellipsis?: string): null
```

**Parameters:**

- `input: null` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `null` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

```typescript
import { truncate } from '@helpers4/string';

truncate(input: string, maxLength: number, ellipsis?: string): string
```

**Parameters:**

- `input: string` — The string to truncate.
- `maxLength: number` — Maximum number of characters in the output (including ellipsis).
- `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`.

**Returns:** `string` — The (possibly truncated) string, or the input itself when `null`/`undefined`.

**Examples:**

*Truncate with default ellipsis*

Appends … when the string exceeds the limit.

```typescript
truncate('Hello, world!', 8)
// => 'Hello, …'
```

*Truncate with custom ellipsis*

The ellipsis counts toward the maxLength.

```typescript
truncate('Hello, world!', 8, '...')
// => 'Hello...'
```

*String within limit*

Returned unchanged when already short enough.

```typescript
truncate('Hi', 10)
// => 'Hi'
```

---

### `unescapeHtml`

Unescapes the HTML entities `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;`
back to `&`, `<`, `>`, `"`, and `'`.

This is the exact inverse of escapeHtml — it only recognizes the
five entities that function produces, not the full HTML entity set (no
`&nbsp;`, no numeric code points beyond `&#39;`, etc.).

```typescript
import { unescapeHtml } from '@helpers4/string';

unescapeHtml(str: string): string
```

**Parameters:**

- `str: string` — The string to unescape

**Returns:** `string` — The unescaped string

**Examples:**

*Unescape HTML entities back to their original characters*

Inverse of escapeHtml — turns &lt;, &gt;, &amp;, &quot;, &#39; back into < > & " '.

```typescript
unescapeHtml('&lt;b&gt;bold&lt;/b&gt;')
// => '<b>bold</b>'
```

*Unrecognized entities are left untouched*

Only the five entities escapeHtml produces are unescaped — not the full HTML entity set.

```typescript
unescapeHtml('a&nbsp;b')
// => 'a&nbsp;b'  (unchanged)
```

---

### `words`

Splits a string into an array of words.
`null` and `undefined` return `[]`.

Handles camelCase, PascalCase, SCREAMING_SNAKE_CASE, kebab-case,
snake_case, and regular whitespace-separated text. Numbers are
treated as word tokens.

```typescript
import { words } from '@helpers4/string';

words(str: string | null | undefined): string[]
```

**Parameters:**

- `str: string | null | undefined` — The string to split into words.

**Returns:** `string[]` — An array of word tokens.

**Examples:**

*Split common string formats*

Splits camelCase, PascalCase, snake_case, kebab-case and space-separated words.

```typescript
words('camelCaseString') // => ['camel', 'Case', 'String']
words('snake_case')       // => ['snake', 'case']
words('kebab-case')       // => ['kebab', 'case']
words('hello world')      // => ['hello', 'world']
```

*Build camelCase from any input*

Combine with a map to convert from any naming convention.

```typescript
const toCamel = (str: string) =>
  words(str)
    .map((w, i) => i === 0 ? w.toLowerCase() : w[0].toUpperCase() + w.slice(1).toLowerCase())
    .join('');
toCamel('hello-world'); // => 'helloWorld'
```

---

## type

Package: `@helpers4/type`

### `Brand`

Brands a base type `T` with a phantom tag `B` to create a nominal type.

Two `Brand<string, 'UserId'>` and `Brand<string, 'Email'>` are structurally
identical strings at runtime, but TypeScript treats them as distinct types
at the call site — preventing accidental mix-ups.

Use a const-assertion cast at the creation boundary:
```ts
type UserId = Brand<string, 'UserId'>;
const toUserId = (s: string): UserId => s as UserId;
```

**Examples:**

*Brand*

```typescript
```ts
type Meter = Brand<number, 'Meter'>;
type Second = Brand<number, 'Second'>;

declare function speed(distance: Meter, time: Second): number;
const d = 100 as Meter;
const t = 5 as Second;
speed(d, t);   // ✅
speed(t, d);   // ✗ Type error — args swapped
```
```

---

### `DeepGet`

Resolves the value type at a given `Path` within `T`.

Returns `unknown` when any key in `Path` is not present in the corresponding
level of `T`. An empty path resolves to `T` itself. A path segment that goes
through an optional property keeps the result nullable (`V | undefined`)
instead of degrading to `unknown`.

**Examples:**

*DeepGet*

```typescript
```ts
type Obj = { a: { b: { c: number } } };

DeepGet<Obj, ['a', 'b', 'c']> // => number
DeepGet<Obj, ['a', 'b']>      // => { c: number }
DeepGet<Obj, ['a', 'x']>      // => unknown
DeepGet<Obj, []>              // => Obj

type WithOptional = { a?: { b: number } };
DeepGet<WithOptional, ['a', 'b']> // => number | undefined
```
```

---

### `DeepPartial`

Recursively makes all properties of T optional, including nested objects
and array elements.

**Examples:**

*DeepPartial*

```typescript
```ts
type Config = { server: { host: string; port: number }; debug: boolean };
type PartialConfig = DeepPartial<Config>;
// => { server?: { host?: string; port?: number }; debug?: boolean }
```
```

---

### `DeepSet`

Produces the type of `T` after replacing the value at `Path` with `V`.

When a key in `Path` is absent from the corresponding level of `T`, that level
(and everything below it) is added as a new field instead of resolving to
`never` — mirroring how `set()` creates intermediate objects at runtime.

**Examples:**

*DeepSet*

```typescript
```ts
type Obj = { a: { b: number; c: string } };

DeepSet<Obj, ['a', 'b'], string>
// => { a: { b: string; c: string } }

DeepSet<Obj, ['a', 'x'], boolean>
// => { a: { b: number; c: string } & { x: boolean } }
```
```

---

### `DeepWritable`

Recursively removes `readonly` from all properties of T, including nested
objects, array elements, and tuple positions.

**Examples:**

*DeepWritable*

```typescript
```ts
type Config = { readonly server: { readonly host: string }; readonly tags: readonly string[] };
type MutableConfig = DeepWritable<Config>;
// => { server: { host: string }; tags: string[] }
```
```

*DeepWritable*

```typescript
```ts
type Point = readonly [x: number, y: number];
type MutablePoint = DeepWritable<Point>;
// => [x: number, y: number]

Note: `Date`, `Map`, `Set`, `Promise`, and `RegExp` are treated as opaque and passed
through unchanged. In particular, `DeepWritable<Map<K, V>>` does **not** strip `readonly`
from the value type `V` — use a manual mapped type if you need that.
```
```

---

### `KeysOfType`

Extracts the keys of `T` whose values extend `V`.

Optional properties are matched by their non-nullable value type, so an
optional `string` property still counts as a `string` key.

**Examples:**

*KeysOfType*

```typescript
```ts
type User = { id: number; name: string; email: string; active: boolean };
type StringKeys = KeysOfType<User, string>; // 'name' | 'email'
type NumberKeys = KeysOfType<User, number>; // 'id'
```
```

---

### `Maybe`

Type for values that can be T, undefined, or null.

---

### `Nullable`

Adds `null` to a type (`T | null`).

Useful as a shorthand when explicit nullability should be expressed
in function signatures or generic constraints.

**Examples:**

*Nullable*

```typescript
```ts
type MaybeUser = Nullable<User>; // User | null

function findUser(id: string): Nullable<User> { ... }
```
```

---

### `Nullish`

Adds `null` and `undefined` to a type (`T | null | undefined`).

Alias of Maybe.

**Examples:**

*Nullish*

```typescript
```ts
type OptionalUser = Nullish<User>; // User | null | undefined
```
```

---

### `OmitByValue`

Constructs a type by omitting all entries of `T` whose values extend `V`.

Optional properties are matched by their non-nullable value type, so an
optional `string` property is omitted the same as a required one.

**Examples:**

*OmitByValue*

```typescript
```ts
type Form = { name: string; age: number; email: string; active: boolean };
type NonStringFields = OmitByValue<Form, string>; // { age: number; active: boolean }
```
```

---

### `OptionalKeys`

Extracts the optional keys of an object type `T`.

**Examples:**

*OptionalKeys*

```typescript
```ts
type User = { id: number; name: string; email?: string };
type Opts = OptionalKeys<User>; // 'email'
```
```

---

### `PickByValue`

Constructs a type by picking all entries of `T` whose values extend `V`.

Optional properties are matched by their non-nullable value type, so an
optional `string` property is picked the same as a required one.

**Examples:**

*PickByValue*

```typescript
```ts
type Form = { name: string; age: number; email: string; active: boolean };
type StringFields = PickByValue<Form, string>; // { name: string; email: string }
```
```

---

### `Prettify`

Flattens an intersection type into a single readable object type.

IDE tooltips for intersections like `A & B & C` often show the raw
intersection instead of the resolved shape. Wrapping with `Prettify`
forces TypeScript to expand and display the fully-resolved type.

Distributes over unions, so each member is prettified independently
instead of collapsing to their shared keys.

**Examples:**

*Prettify*

```typescript
```ts
type A = { a: number };
type B = { b: string };
type Merged = A & B;        // shown as "A & B" in IDE
type Pretty = Prettify<Merged>; // shown as "{ a: number; b: string }"
```
```

---

### `RequiredKeys`

Extracts the required (non-optional) keys of an object type `T`.

**Examples:**

*RequiredKeys*

```typescript
```ts
type User = { id: number; name: string; email?: string };
type Required = RequiredKeys<User>; // 'id' | 'name'
```
```

---

### `UnionToIntersection`

Converts a union type to an intersection type: `A | B | C` → `A & B & C`.

Uses conditional-type distribution and the contravariant position of a
function parameter to collapse the union into an intersection.

**Examples:**

*UnionToIntersection*

```typescript
```ts
type Union = { a: number } | { b: string } | { c: boolean };
type Intersection = UnionToIntersection<Union>;
// { a: number } & { b: string } & { c: boolean }

type Keys = UnionToIntersection<'a' | 'b'>;
// 'a' & 'b'  (resolves to never for disjoint literals)
```
```

---

### `ValueOf`

Produces a union of all value types of an object type `T`.

**Examples:**

*ValueOf*

```typescript
```ts
type Config = { host: string; port: number; secure: boolean };
type ConfigValue = ValueOf<Config>; // string | number | boolean

const STATUS = { OK: 200, NOT_FOUND: 404, ERROR: 500 } as const;
type StatusCode = ValueOf<typeof STATUS>; // 200 | 404 | 500
```
```

---

## url

Package: `@helpers4/url`

### `cleanPath`

Clean an URL by removing duplicate slashes.
The protocol part of the URL is not modified.

```typescript
import { cleanPath } from '@helpers4/url';

cleanPath(url: string | null | undefined): string | null | undefined
```

**Parameters:**

- `url: string | null | undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string | null | undefined` — The cleaned URL string, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Remove duplicate slashes*

Cleans an URL by removing duplicate slashes while preserving the protocol.

```typescript
cleanPath('/path//to///resource')
// => '/path/to/resource'
```

*Preserve protocol*

The double slash after the protocol (http://) is not modified.

```typescript
cleanPath('http://example.com//path')
// => 'http://example.com/path'
```

*Handle null and undefined*

Returns null for null input and undefined for undefined input.

```typescript
cleanPath(null)      // => null
cleanPath(undefined) // => undefined
```

---

### `extractPureURI`

Extracts the pure URI from a URL by removing query parameters and fragments.

```typescript
import { extractPureURI } from '@helpers4/url';

extractPureURI(url: string): string
```

**Parameters:**

- `url: string` — The URL string to process

**Returns:** `string` — The URI without query parameters and fragments, or the original value if undefined/null

```typescript
import { extractPureURI } from '@helpers4/url';

extractPureURI(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to process

**Returns:** `undefined` — The URI without query parameters and fragments, or the original value if undefined/null

```typescript
import { extractPureURI } from '@helpers4/url';

extractPureURI(url: null): null
```

**Parameters:**

- `url: null` — The URL string to process

**Returns:** `null` — The URI without query parameters and fragments, or the original value if undefined/null

**Examples:**

*Remove query parameters and fragments*

Strips everything after ? or # from the URL.

```typescript
extractPureURI('https://example.com/path?query=1#section')
// => 'https://example.com/path'
```

---

### `onlyPath`

Extract only the path from an URI with optional query and fragments.

For example, all these parameters will return `/path`:
 - `/path`
 - `/path?query=thing`
 - `/path#fragment`
 - `/path?query=thing#fragment`

```typescript
import { onlyPath } from '@helpers4/url';

onlyPath(url: string): string
```

**Parameters:**

- `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { onlyPath } from '@helpers4/url';

onlyPath(url: null): null
```

**Parameters:**

- `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `null` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { onlyPath } from '@helpers4/url';

onlyPath(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `undefined` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Extract the path from a URL*

Strips query parameters and fragments from a URL path.

```typescript
onlyPath('/path?query=thing#fragment')
// => '/path'
```

---

### `parsePackageRepository`

Parse the `repository` field from `package.json` into a structured object.

Supports all npm-specified formats:
- **Object form**: `{ "type": "git", "url": "...", "directory": "..." }`
- **GitHub shorthand**: `"owner/repo"` or `"github:owner/repo"`
- **Platform shorthands**: `"gitlab:owner/repo"`, `"bitbucket:owner/repo"`
- **Gist shorthand**: `"gist:<id>"`
- **URL forms**: `git+https://`, `https://`, `git://`, `git@` SSH, `git+ssh://`

Returns `undefined` for `null`, `undefined`, arrays, or values that cannot
be matched to any recognised format.

```typescript
import { parsePackageRepository } from '@helpers4/url';

parsePackageRepository(repository: unknown): PackageRepository | undefined
```

**Parameters:**

- `repository: unknown` — The `repository` field value from `package.json`.

**Returns:** `PackageRepository | undefined` — A parsed PackageRepository object, or `undefined` if the
  input cannot be parsed.

**Examples:**

*Parse the npm canonical object form*

Parses the full object form written by npm publish.

```typescript
parsePackageRepository({ type: 'git', url: 'git+https://github.com/helpers4/typescript.git' })
// => { type: 'git', host: 'github', slug: 'helpers4/typescript',
//      owner: 'helpers4', repo: 'typescript', gistId: undefined, directory: undefined }
```

*Parse npm shorthand forms*

npm accepts "owner/repo", "github:owner/repo", "gitlab:owner/repo" etc. as shorthand.

```typescript
parsePackageRepository('helpers4/typescript')
// => { host: 'github', slug: 'helpers4/typescript', owner: 'helpers4', repo: 'typescript', ... }

parsePackageRepository('gitlab:myorg/myproject')
// => { host: 'gitlab', slug: 'myorg/myproject', owner: 'myorg', repo: 'myproject', ... }

parsePackageRepository('gist:11081aaa281')
// => { host: 'gist', gistId: '11081aaa281', slug: undefined, owner: undefined, ... }
```

---

### `relativeURLToAbsolute`

Converts a relative URL to an absolute URL using the current document base URI.

```typescript
import { relativeURLToAbsolute } from '@helpers4/url';

relativeURLToAbsolute(relativeUrl: string): string
```

**Parameters:**

- `relativeUrl: string` — The relative URL to convert

**Returns:** `string` — The absolute URL

**Examples:**

*Convert a relative URL to absolute*

Prepends the base URI to a relative path, cleaning duplicate slashes.

```typescript
relativeURLToAbsolute('/api/data')
// => 'http://localhost/api/data' (depends on document.baseURI)
```

---

### `withLeadingSlash`

Adds a leading slash `/` to the given URL if it is not already present.

This function is useful for ensuring that URLs are properly formatted
with a leading slash, which is often required in web development for
consistency and to avoid issues with relative paths.

```typescript
import { withLeadingSlash } from '@helpers4/url';

withLeadingSlash(url: string): string
```

**Parameters:**

- `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withLeadingSlash } from '@helpers4/url';

withLeadingSlash(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `undefined` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withLeadingSlash } from '@helpers4/url';

withLeadingSlash(url: null): null
```

**Parameters:**

- `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `null` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Add a leading slash*

Ensures the URL starts with a forward slash.

```typescript
withLeadingSlash('path/to/resource')
// => '/path/to/resource'
```

*Already has leading slash*

Does not add a duplicate slash.

```typescript
withLeadingSlash('/already/has/slash')
// => '/already/has/slash'
```

---

### `withoutLeadingSlash`

Removes the leading slash `/` from the given URL if it is present.

This function is useful for ensuring that URLs are properly formatted
without a leading slash, which is often required in web development for
consistency and to avoid issues with relative paths.

```typescript
import { withoutLeadingSlash } from '@helpers4/url';

withoutLeadingSlash(url: string): string
```

**Parameters:**

- `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withoutLeadingSlash } from '@helpers4/url';

withoutLeadingSlash(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `undefined` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withoutLeadingSlash } from '@helpers4/url';

withoutLeadingSlash(url: null): null
```

**Parameters:**

- `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `null` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Remove leading slash*

Strips the leading slash from a URL path.

```typescript
withoutLeadingSlash('/path/to/resource')
// => 'path/to/resource'
```

---

### `withoutTrailingSlash`

Removes the trailing slash `/` from the given URL if it is present.

This function is useful for ensuring that URLs are properly formatted
without a trailing slash, which is often required in web development for
consistency and to avoid issues with relative paths.

```typescript
import { withoutTrailingSlash } from '@helpers4/url';

withoutTrailingSlash(url: string): string
```

**Parameters:**

- `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withoutTrailingSlash } from '@helpers4/url';

withoutTrailingSlash(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `undefined` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withoutTrailingSlash } from '@helpers4/url';

withoutTrailingSlash(url: null): null
```

**Parameters:**

- `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `null` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Remove trailing slash*

Strips the trailing slash from a URL path.

```typescript
withoutTrailingSlash('path/to/resource/')
// => 'path/to/resource'
```

---

### `withTrailingSlash`

Adds a trailing slash `/` to the given URL if it is not already present.

This function is useful for ensuring that URLs are properly formatted
with a trailing slash, which is often required in web development for
consistency and to avoid issues with relative paths.

```typescript
import { withTrailingSlash } from '@helpers4/url';

withTrailingSlash(url: string): string
```

**Parameters:**

- `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `string` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withTrailingSlash } from '@helpers4/url';

withTrailingSlash(url: undefined): undefined
```

**Parameters:**

- `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `undefined` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

```typescript
import { withTrailingSlash } from '@helpers4/url';

withTrailingSlash(url: null): null
```

**Parameters:**

- `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`.

**Returns:** `null` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`.

**Examples:**

*Add a trailing slash*

Ensures the URL ends with a forward slash.

```typescript
withTrailingSlash('path/to/resource')
// => 'path/to/resource/'
```

---

## version

Package: `@helpers4/version`

### `compare`

Compares two semantic version strings according to SemVer 2.0.0 specification

Supports:
- Core version: MAJOR.MINOR.PATCH
- Pre-release: -alpha, -beta.1, -rc.1, etc.
- Build metadata: +build, +sha.abc123 (ignored in comparison per spec)
- Optional 'v' prefix

```typescript
import { compare } from '@helpers4/version';

compare(version1: string, version2: string): number
```

**Parameters:**

- `version1: string` — First version string
- `version2: string` — Second version string

**Returns:** `number` — -1 if version1 < version2, 0 if equal, 1 if version1 > version2

**Examples:**

*Compare two semver versions*

Returns -1, 0, or 1 based on SemVer ordering.

```typescript
compare('1.0.0', '2.0.0') // => -1
compare('1.0.0', '1.0.0') // => 0
compare('2.0.0', '1.0.0') // => 1
```

*Prerelease is lower than release*

A prerelease version is always less than the release.

```typescript
compare('1.0.0-alpha', '1.0.0')
// => -1
```

---

### `increment`

Increments a semantic version

```typescript
import { increment } from '@helpers4/version';

increment(version: string, type: "major" | "minor" | "patch"): string
```

**Parameters:**

- `version: string` — The version to increment
- `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch')

**Returns:** `string` — Incremented version string

```typescript
import { increment } from '@helpers4/version';

increment(version: undefined, type: "major" | "minor" | "patch"): undefined
```

**Parameters:**

- `version: undefined` — The version to increment
- `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch')

**Returns:** `undefined` — Incremented version string

```typescript
import { increment } from '@helpers4/version';

increment(version: null, type: "major" | "minor" | "patch"): null
```

**Parameters:**

- `version: null` — The version to increment
- `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch')

**Returns:** `null` — Incremented version string

**Examples:**

*Increment the patch version*

Bumps the patch number while keeping major and minor.

```typescript
increment('1.2.3', 'patch')
// => '1.2.4'
```

*Increment the minor version*

Bumps the minor number and resets patch to 0.

```typescript
increment('1.2.3', 'minor')
// => '1.3.0'
```

*Preserve the v prefix*

The v prefix is preserved if present in the input.

```typescript
increment('v1.0.0', 'major')
// => 'v2.0.0'
```

---

### `incrementPrerelease`

Increments the prerelease portion of a semantic version — the semantics `npm version
prerelease --preid <id>` uses, not covered by increment (which only handles
`'major' | 'minor' | 'patch'`).

- No current prerelease (a release version) → bumps `patch` and starts a new prerelease line
  at `<prereleaseId>.0` (a prerelease of the version itself, e.g. `1.2.3`, would already be
  released).
- Same prerelease type as the current version → increments its counter.
- Different prerelease type (e.g. `alpha` → `beta`) → resets the counter to `0`.

Input prerelease can be any shape, but only the first two parts are considered;
output is always normalized to `<prereleaseId>.<number>`. Build metadata, if any, is
dropped — it's tied to the specific build that produced the input version, not the new one.
A leading `v` is preserved if present, matching increment's behavior (`parse`/
`stringify` alone would strip it — see their docs).

```typescript
import { incrementPrerelease } from '@helpers4/version';

incrementPrerelease(version: string, prereleaseId: string): string
```

**Parameters:**

- `version: string` — The version to increment
- `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`)

**Returns:** `string` — The version with an incremented or newly-started prerelease

```typescript
import { incrementPrerelease } from '@helpers4/version';

incrementPrerelease(version: undefined, prereleaseId: string): undefined
```

**Parameters:**

- `version: undefined` — The version to increment
- `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`)

**Returns:** `undefined` — The version with an incremented or newly-started prerelease

```typescript
import { incrementPrerelease } from '@helpers4/version';

incrementPrerelease(version: null, prereleaseId: string): null
```

**Parameters:**

- `version: null` — The version to increment
- `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`)

**Returns:** `null` — The version with an incremented or newly-started prerelease

**Examples:**

*Start a new alpha line*

Bumping a release version starts a fresh prerelease at .0, one patch ahead.

```typescript
incrementPrerelease('1.2.3', 'alpha')
// => '1.2.4-alpha.0'
```

*Increment the same prerelease type*

Bumping with the same prereleaseId increments its counter.

```typescript
incrementPrerelease('1.2.4-alpha.0', 'alpha')
// => '1.2.4-alpha.1'
```

*Switch prerelease type*

Switching to a different prereleaseId resets the counter to 0, e.g. graduating from alpha to beta.

```typescript
incrementPrerelease('1.2.4-alpha.3', 'beta')
// => '1.2.4-beta.0'
```

---

### `isPrerelease`

Returns `true` when the version string has a prerelease suffix
(i.e. contains a `-` after the core `MAJOR.MINOR.PATCH`).

```typescript
import { isPrerelease } from '@helpers4/version';

isPrerelease(version: string): boolean
```

**Parameters:**

- `version: string` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`).

**Returns:** `boolean` — `true` if the version is a prerelease, `false` otherwise.

```typescript
import { isPrerelease } from '@helpers4/version';

isPrerelease(version: ParsedVersion): boolean
```

**Parameters:**

- `version: ParsedVersion` — A ParsedVersion object (as returned by parse).

**Returns:** `boolean` — `true` if `version.prerelease` is non-empty, `false` otherwise.

```typescript
import { isPrerelease } from '@helpers4/version';

isPrerelease(version: undefined): undefined
```

**Parameters:**

- `version: undefined` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`).

**Returns:** `undefined` — `true` if the version is a prerelease, `false` otherwise.

```typescript
import { isPrerelease } from '@helpers4/version';

isPrerelease(version: null): null
```

**Parameters:**

- `version: null` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`).

**Returns:** `null` — `true` if the version is a prerelease, `false` otherwise.

**Examples:**

*Detect a prerelease version*

Returns true for any version string that contains a prerelease suffix.

```typescript
isPrerelease('2.0.0-alpha.1') // true
isPrerelease('1.0.0-rc.0')   // true
```

*Stable versions return false*

Returns false when the version has no prerelease suffix.

```typescript
isPrerelease('1.0.0') // false
isPrerelease('2.1.3') // false
```

*Accept a ParsedVersion object*

Works with the result of parse() — checks the prerelease array instead of string matching.

```typescript
isPrerelease(parse('2.0.0-alpha.1')) // true
isPrerelease(parse('1.0.0'))         // false
```

---

### `parse`

Parses a semantic version string into its components according to SemVer 2.0.0 specification

Supports:
- Core version: MAJOR.MINOR.PATCH
- Pre-release: -alpha, -beta.1, -rc.1, -0.3.7, -x.7.z.92
- Build metadata: +build, +sha.abc123, +20130313144700
- Optional 'v' prefix (commonly used in git tags)

```typescript
import { parse } from '@helpers4/version';

parse(version: string): ParsedVersion
```

**Parameters:**

- `version: string` — Version string to parse

**Returns:** `ParsedVersion` — Parsed version object with major, minor, patch, prerelease, and build

```typescript
import { parse } from '@helpers4/version';

parse(version: undefined): undefined
```

**Parameters:**

- `version: undefined` — Version string to parse

**Returns:** `undefined` — Parsed version object with major, minor, patch, prerelease, and build

```typescript
import { parse } from '@helpers4/version';

parse(version: null): null
```

**Parameters:**

- `version: null` — Version string to parse

**Returns:** `null` — Parsed version object with major, minor, patch, prerelease, and build

**Examples:**

*Parse a semver string*

Breaks a semantic version string into its components.

```typescript
parse('1.2.3')
// => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] }
```

*Parse a prerelease version*

Handles prerelease identifiers and optional v prefix.

```typescript
parse('v2.0.0-alpha.1')
// => { major: 2, minor: 0, patch: 0, prerelease: ['alpha', '1'], build: [] }
```

---

### `satisfiesRange`

Checks if a version satisfies a range (simple implementation)

```typescript
import { satisfiesRange } from '@helpers4/version';

satisfiesRange(version: string, range: string): boolean
```

**Parameters:**

- `version: string` — Version to check
- `range: string` — Range pattern (e.g., ">=1.0.0", "~1.2.0", "^1.0.0")

**Returns:** `boolean` — True if version satisfies the range

**Examples:**

*Check caret range*

Caret (^) allows patch and minor updates within the same major.

```typescript
satisfiesRange('1.2.3', '^1.0.0')
// => true
```

*Check greater-than-or-equal range*

The >= operator checks if the version is at least the specified value.

```typescript
satisfiesRange('2.0.0', '>=1.5.0')
// => true
```

*Out of range*

Returns false when the version does not satisfy the range.

```typescript
satisfiesRange('0.9.0', '>=1.0.0')
// => false
```

---

### `stringify`

Reconstruct a semantic version string from a ParsedVersion object.

This is the inverse of parse:
`stringify(parse(v)) === stripV(v)` for any valid SemVer string `v`.

```typescript
import { stringify } from '@helpers4/version';

stringify(parsed: ParsedVersion): string
```

**Parameters:**

- `parsed: ParsedVersion` — A parsed semantic version object.

**Returns:** `string` — The reconstructed version string (without leading `v`).

```typescript
import { stringify } from '@helpers4/version';

stringify(parsed: undefined): undefined
```

**Parameters:**

- `parsed: undefined` — A parsed semantic version object.

**Returns:** `undefined` — The reconstructed version string (without leading `v`).

```typescript
import { stringify } from '@helpers4/version';

stringify(parsed: null): null
```

**Parameters:**

- `parsed: null` — A parsed semantic version object.

**Returns:** `null` — The reconstructed version string (without leading `v`).

**Examples:**

*Reconstruct a stable version*

Converts a ParsedVersion object back to a version string.

```typescript
stringify({ major: 1, minor: 2, patch: 3, prerelease: [], build: [] })
// => '1.2.3'
```

*Round-trip with parse*

stringify(parse(v)) returns the original version string (without leading v).

```typescript
stringify(parse('2.0.0-alpha.1'))
// => '2.0.0-alpha.1'

stringify(parse('1.0.0-beta+exp.sha.5114f85'))
// => '1.0.0-beta+exp.sha.5114f85'
```

---

### `stripV`

Strip the leading "v" from a version string if it exists.

```typescript
import { stripV } from '@helpers4/version';

stripV(version: string): string
```

**Parameters:**

- `version: string` — The version string to process

**Returns:** `string` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v"

```typescript
import { stripV } from '@helpers4/version';

stripV(version: null): null
```

**Parameters:**

- `version: null` — The version string to process

**Returns:** `null` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v"

```typescript
import { stripV } from '@helpers4/version';

stripV(version: undefined): undefined
```

**Parameters:**

- `version: undefined` — The version string to process

**Returns:** `undefined` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v"

**Examples:**

*Remove v prefix from a version string*

Strips the leading "v" from a git tag-style version string.

```typescript
stripV('v1.2.3')
// => '1.2.3'
```

*No-op when there is no v prefix*

Returns the string unchanged when it does not start with "v".

```typescript
stripV('1.2.3')
// => '1.2.3'
```

---
