# @helpers4/all

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

## About

helpers4 provides ~199 battle-tested utility functions across 15 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/commit
pnpm add @helpers4/date
pnpm add @helpers4/function
pnpm add @helpers4/id
pnpm add @helpers4/markdown
pnpm add @helpers4/number
pnpm add @helpers4/object
pnpm add @helpers4/observable
pnpm add @helpers4/promise
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 |
| `@helpers4/array` | `compact` | Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. |
| `@helpers4/array` | `countBy` | Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key |
| `@helpers4/array` | `createSortByDateFn` | Creates a sort function for objects by date property |
| `@helpers4/array` | `createSortByNumberFn` | Creates a sort function for objects by number property |
| `@helpers4/array` | `createSortByStringFn` | Creates a sort function for objects by string property |
| `@helpers4/array` | `difference` | Returns the difference between two arrays (items in first array but not in second) |
| `@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` | `intersection` | Compute the intersection of two arrays, meaning the elements that are present in both arrays. |
| `@helpers4/array` | `intersects` | Simple helper that check if two lists shared at least an item in common. |
| `@helpers4/array` | `partition` | Splits an array into two groups based on a predicate function. The first group contains elements for |
| `@helpers4/array` | `range` | Generates an array of sequential numbers from start to end (exclusive). If only one argument is prov |
| `@helpers4/array` | `sample` | Picks one or more random elements from an array. When called without a count, returns a single eleme |
| `@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` | `unique` | Removes duplicate values from an array |
| `@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` | `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/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` | `dateToISOString` | Formats a date to ISO string or returns null. |
| `@helpers4/date` | `daysDifference` | Gets the difference in days between two dates. |
| `@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` | `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` | `safeDate` | Safely creates a Date object from various input types. |
| `@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 |
| `@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/id` | `uuid7` | Generates a UUID v7 string (RFC 9562). UUID v7 embeds a Unix timestamp in milliseconds, making it ch |
| `@helpers4/markdown` | `escape` | Escapes all Markdown special characters in a string so they render as literal text rather than forma |
| `@helpers4/number` | `clamp` | Clamps a number between min and max values |
| `@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` | `lerp` | Linearly interpolates between `start` and `end` by the factor `t`.  - `t = 0` returns `start`. - `t  |
| `@helpers4/number` | `mean` | Calculates the arithmetic mean (average) of an array of numbers. Returns `NaN` for an empty array.   |
| `@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/number` | `sum` | Calculates the sum of an array of numbers. |
| `@helpers4/object` | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec |
| `@helpers4/object` | `deepClone` | Creates a deep copy of an object or array |
| `@helpers4/object` | `deepMerge` | Merges two or more objects deeply |
| `@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` | `get` | Gets a value from an object using a dot-notated path |
| `@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` | `map` | Transforms the values and/or keys of a plain object in a single pass.  Both callbacks are optional a |
| `@helpers4/object` | `omit` | Creates a new object without the specified keys. |
| `@helpers4/object` | `pick` | Creates a new object with only the specified keys. |
| `@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 using a dot-notated path |
| `@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/promise` | `consoleLogPromise` | Returns a function that logs data to the console and passes it through. |
| `@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` | `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` | `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/string` | `camelCase` | Converts kebab-case to camelCase |
| `@helpers4/string` | `capitalize` | Capitalizes the first letter of a string. By default, lowercases the remaining characters. Pass `{ l |
| `@helpers4/string` | `escapeHtml` | Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string.  Use this to safely emb |
| `@helpers4/string` | `extractErrorMessage` | Convert an error to a readable message. |
| `@helpers4/string` | `injectWordBreaks` | Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as sid |
| `@helpers4/string` | `kebabCase` | Converts camelCase to kebab-case |
| `@helpers4/string` | `leadingSentence` | Extracts the leading sentence from a string.  A sentence boundary is detected at the first occurrenc |
| `@helpers4/string` | `pascalCase` | Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed format |
| `@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` | `words` | Splits a string into an array of words.  Handles camelCase, PascalCase, SCREAMING_SNAKE_CASE, kebab- |
| `@helpers4/type` | `isArray` | Checks if a value is an array. |
| `@helpers4/type` | `isArrayBuffer` | Checks if a value is an ArrayBuffer instance.  Useful for filtering or type-narrowing in a functiona |
| `@helpers4/type` | `isAsyncFunction` | Checks if a value is an async function.  Returns `true` for any function declared with `async`. |
| `@helpers4/type` | `isBigInt` | Checks if a value is a bigint. |
| `@helpers4/type` | `isBlob` | Checks if a value is a Blob instance.  Useful for filtering or type-narrowing in a functional pipeli |
| `@helpers4/type` | `isBoolean` | Checks if a value is a boolean. |
| `@helpers4/type` | `isBuffer` | Checks if a value is a Node.js Buffer instance.  `Buffer` extends `Uint8Array` and is specific to No |
| `@helpers4/type` | `isDate` | Checks if a value is a Date instance.  Note: this only checks the type, not whether the Date is vali |
| `@helpers4/type` | `isDefined` | Checks if a value is defined (not undefined nor null). This is the inverse of isNullish.  Use as a t |
| `@helpers4/type` | `isEmpty` | Checks if a value is empty.  Supported types: - `null` / `undefined` → empty - `string` → length === |
| `@helpers4/type` | `isError` | Checks if a value is an Error instance. |
| `@helpers4/type` | `isFalsy` | Checks if a value is falsy (`false`, `null`, `undefined`, `0`, `""`, `NaN`). |
| `@helpers4/type` | `isFormData` | Checks if a value is a FormData instance.  Useful for filtering or type-narrowing in a functional pi |
| `@helpers4/type` | `isFunction` | Checks if a value is a function. |
| `@helpers4/type` | `isIterable` | Checks if a value is iterable (has a `Symbol.iterator` method).  Returns `true` for strings, arrays, |
| `@helpers4/type` | `isMap` | Checks if a value is a Map instance. |
| `@helpers4/type` | `isNegativeNumber` | Checks if a value is a number less than 0.  Returns `false` for `NaN`, `0`, positive numbers, and no |
| `@helpers4/type` | `isNonEmptyArray` | Checks if a value is a non-empty array (length > 0). |
| `@helpers4/type` | `isNonEmptyString` | Checks if a value is a non-empty string (length > 0). |
| `@helpers4/type` | `isNull` | Checks if a value is `null`. |
| `@helpers4/type` | `isNullish` | Checks if a value is null or undefined (nullish). |
| `@helpers4/type` | `isNumber` | Checks if a value is a number.  Returns `false` for `NaN`, which intentionally deviates from `typeof |
| `@helpers4/type` | `isPlainObject` | Checks if a value is a plain object.  A plain object is created by `{}`, `new Object()`, or `Object. |
| `@helpers4/type` | `isPositiveNumber` | Checks if a value is a number greater than 0.  Returns `false` for `NaN`, `0`, negative numbers, and |
| `@helpers4/type` | `isPrimitive` | Checks if a value is a JavaScript primitive.  Primitive types: `string`, `number`, `boolean`, `bigin |
| `@helpers4/type` | `isPromise` | Checks if a value is a Promise or a thenable.  Returns `true` for any object that has `.then()` and  |
| `@helpers4/type` | `isRegExp` | Checks if a value is a RegExp instance. |
| `@helpers4/type` | `isSpecialObject` | Determines if a value is a special object that should not have its properties compared deeply. Speci |
| `@helpers4/type` | `isString` | Checks if a value is a string. |
| `@helpers4/type` | `isSymbol` | Checks if a value is a symbol. |
| `@helpers4/type` | `isTemporalDuration` | Checks if a value is a `Temporal.Duration`.  Uses `instanceof` when `Temporal` is available globally |
| `@helpers4/type` | `isTemporalInstant` | Checks if a value is a `Temporal.Instant`.  Uses `instanceof` when `Temporal` is available globally, |
| `@helpers4/type` | `isTemporalPlainDate` | Checks if a value is a `Temporal.PlainDate`.  Uses `instanceof` when `Temporal` is available globall |
| `@helpers4/type` | `isTemporalPlainDateTime` | Checks if a value is a `Temporal.PlainDateTime`.  Uses `instanceof` when `Temporal` is available glo |
| `@helpers4/type` | `isTemporalPlainTime` | Checks if a value is a `Temporal.PlainTime`.  Uses `instanceof` when `Temporal` is available globall |
| `@helpers4/type` | `isTemporalZonedDateTime` | Checks if a value is a `Temporal.ZonedDateTime`.  Uses `instanceof` when `Temporal` is available glo |
| `@helpers4/type` | `isTimestamp` | Checks if a value is a valid timestamp (milliseconds or Unix seconds).  Supports: - JavaScript / Jav |
| `@helpers4/type` | `isTruthy` | Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`).  This is the ty |
| `@helpers4/type` | `isUndefined` | Checks if a value is `undefined`. |
| `@helpers4/type` | `isValidDate` | Checks if a value is a valid Date instance (not `Invalid Date`).  Unlike isDate, this also verifies  |
| `@helpers4/type` | `isValidRegex` | Checks if a string is a valid regex pattern. |
| `@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` | `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

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

chunk<T>(array: T[], size: number): T[][]
```

**Parameters:**

- `array: T[]` — 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)
// => []
```

---

### `compact`

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

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

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

**Parameters:**

- `array: readonly Falsy | T[]` — 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.

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

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

**Parameters:**

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

**Returns:** `Partial<Record<K, number>>` — A `Partial<Record<K, number>>` where each key maps to its element count.

**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 }
```

---

### `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')

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

---

### `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')

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

---

### `createSortByStringFn`

Creates a sort function for objects by string property

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

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

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to trying 'value', 'label', 'title', 'description')
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case

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

---

### `difference`

Returns the difference between two arrays (items in first array but not in second)

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

difference<T>(array1: T[], array2: T[]): T[]
```

**Parameters:**

- `array1: T[]` — First array
- `array2: T[]` — 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
```

---

### `intersection`

Compute the intersection of two arrays, meaning the elements that are present
in both arrays.

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

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

**Parameters:**

- `a: readonly T[]` — First array
- `b: readonly T[]` — 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.

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

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

**Parameters:**

- `a: readonly T[]` — One list
- `b: readonly T[]` — 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
```

---

### `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.

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

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

**Parameters:**

- `array: readonly T[]` — 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)
// => [[], []]
```

---

### `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]
```

---

### `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[]): T | undefined
```

**Parameters:**

- `array: readonly T[]` — 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[], count: number): T[]
```

**Parameters:**

- `array: readonly T[]` — 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
```

---

### `shuffle`

Randomly reorders elements of an array using the Fisher-Yates algorithm.
Returns a new array without mutating the original.

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

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

**Parameters:**

- `array: readonly T[]` — 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']
```

*Sort objects by 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' }]
```

---

### `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

---

### `unique`

Removes duplicate values from an array

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

unique<T>(array: T[]): T[]
```

**Parameters:**

- `array: T[]` — 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][]): [A[], B[]]
```

**Parameters:**

- `pairs: readonly [A, B][]` — 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][]): [A[], B[], C[]]
```

**Parameters:**

- `pairs: readonly [A, B, C][]` — 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][]): [A[], B[], C[], D[]]
```

**Parameters:**

- `pairs: readonly [A, B, C, D][]` — 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`).

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

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

**Parameters:**

- `array: readonly T[]` — 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}
```

---

### `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')  // => '⚠️'
```

---

## 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
```

---

### `dateToISOString`

Formats a date to ISO string or returns null.

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

dateToISOString(input: DateLike | null | undefined): string | null
```

**Parameters:**

- `input: DateLike | null | undefined` — Date input

**Returns:** `string | null` — ISO string or null

---

### `daysDifference`

Gets the difference in days between two dates.

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

daysDifference(date1: Date, date2: Date): number
```

**Parameters:**

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

**Returns:** `number` — Number of days difference (rounded)

**Examples:**

*Calculate days between two dates*

Returns the absolute number of days between two dates.

```typescript
daysDifference(new Date('2025-01-01'), new Date('2025-01-10'))
// => 9
```

---

### `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).

```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-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
```

---

### `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
```
```

---

### `safeDate`

Safely creates a Date object from various input types.

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

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

**Parameters:**

- `input: DateLike | null | undefined` — String, number, or Date input

**Returns:** `Date | null` — Valid Date object or null if invalid

---

### `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

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

memoize<A extends unknown[], R>(func: function): function
```

**Parameters:**

- `func: function` — The function to memoize

**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)
```

---

## 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
```

---

## 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\\]'
```

---

## 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
```

---

### `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
```

---

### `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)
```

---

### `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/number';

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
```

---

### `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
```

---

### `sum`

Calculates the sum of an array of numbers.

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

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

**Parameters:**

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

**Returns:** `number` — The sum of all values

**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
```

---

## object

Package: `@helpers4/object`

### `compact`

Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object.

```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' }
```

---

### `deepClone`

Creates a deep copy of an object or array

```typescript
import { deepClone } from '@helpers4/object';

deepClone<T>(obj: T): T
```

**Parameters:**

- `obj: T` — The object to clone

**Returns:** `T` — Deep cloned object

**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 = deepClone(original);
cloned.a.b = 2;
// original.a.b is still 1
```

---

### `deepMerge`

Merges two or more objects deeply

```typescript
import { deepMerge } from '@helpers4/object';

deepMerge<T extends Record<string, unknown>>(target: T, sources: Record<string, unknown>[]): T
```

**Parameters:**

- `target: T` — The target object
- `sources: Record<string, unknown>[]` — The source objects to merge

**Returns:** `T` — The merged object

```typescript
import { deepMerge } from '@helpers4/object';

deepMerge(target: undefined, sources: Record<string, unknown>[]): undefined
```

**Parameters:**

- `target: undefined` — The target object
- `sources: Record<string, unknown>[]` — The source objects to merge

**Returns:** `undefined` — The merged object

```typescript
import { deepMerge } from '@helpers4/object';

deepMerge(target: null, sources: Record<string, unknown>[]): null
```

**Parameters:**

- `target: null` — The target object
- `sources: Record<string, unknown>[]` — The source objects to merge

**Returns:** `null` — The merged object

**Examples:**

*Merge two objects deeply*

Recursively merges source properties into the target object.

```typescript
deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
// => { a: 1, b: { c: 2, d: 3 }, e: 4 }
```

---

### `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
```

---

### `get`

Gets a value from an object using a dot-notated path

```typescript
import { get } from '@helpers4/object';

get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined
```

**Parameters:**

- `obj: unknown` — The object to get value from
- `path: string` — The dot-notated path (e.g., 'a.b.c')
- `defaultValue?: T` — Default value if path doesn't exist

**Returns:** `T | undefined` — The value at the path or default value

**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'
```

---

### `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.

```typescript
import { groupBy } from '@helpers4/object';

groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>
```

**Parameters:**

- `items: readonly T[]` — 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.

```typescript
import { invert } from '@helpers4/object';

invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>
```

**Parameters:**

- `obj: Record<K, V>` — 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'
```

---

### `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).

```typescript
import { map } from '@helpers4/object';

map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>
```

**Parameters:**

- `obj: TObj` — 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 }
```

---

### `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' }
```

---

### `pick`

Creates a new object with only the specified keys.

```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' }
```

---

### `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 using a dot-notated path

```typescript
import { set } from '@helpers4/object';

set(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>
```

**Parameters:**

- `obj: Record<string, unknown>` — The object to set value in
- `path: string` — The dot-notated path (e.g., 'a.b.c')
- `value: unknown` — The value to set

**Returns:** `Record<string, unknown>` — The modified object

**Examples:**

*Set a nested property*

Creates intermediate objects as needed along the dot-notated path.

```typescript
set({}, 'a.b.c', 42)
// => { a: { b: { c: 42 } } }
```

---

## 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 []
```

---

## 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
```

---

### `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.

```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
```

---

### `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: RequestInfo | URL, init?: RequestInit, options: SafeFetchOptions): Promise<T | null>
```

**Parameters:**

- `input: RequestInfo | URL` — 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' }
);
```

---

### `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
```

---

## string

Package: `@helpers4/string`

### `camelCase`

Converts kebab-case to camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: string): string
```

**Parameters:**

- `str: string` — The kebab-case string to convert

**Returns:** `string` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The kebab-case string to convert

**Returns:** `undefined` — String in camelCase

```typescript
import { camelCase } from '@helpers4/string';

camelCase(str: null): null
```

**Parameters:**

- `str: null` — The kebab-case 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'
```

---

### `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;'
```

---

### `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'
```

---

### `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)
```

---

### `kebabCase`

Converts camelCase to kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: string): string
```

**Parameters:**

- `str: string` — The camelCase string to convert

**Returns:** `string` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: undefined): undefined
```

**Parameters:**

- `str: undefined` — The camelCase string to convert

**Returns:** `undefined` — String in kebab-case

```typescript
import { kebabCase } from '@helpers4/string';

kebabCase(str: null): null
```

**Parameters:**

- `str: null` — The camelCase 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.

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

**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'
```

---

### `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'
```

---

### `words`

Splits a string into an array of words.

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): string[]
```

**Parameters:**

- `str: string` — 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`

### `isArray`

Checks if a value is an array.

```typescript
import { isArray } from '@helpers4/type';

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/type';

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)]
```

---

### `isAsyncFunction`

Checks if a value is an async function.

Returns `true` for any function declared with `async`.

```typescript
import { isAsyncFunction } from '@helpers4/type';

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
```
```

---

### `isBigInt`

Checks if a value is a bigint.

```typescript
import { isBigInt } from '@helpers4/type';

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/type';

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/type';

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
```
```

---

### `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/type';

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]
```

---

### `isDate`

Checks if a value is a Date instance.

Note: this only checks the type, not whether the Date is valid.
Use isValidDate to also validate that the Date is not `Invalid Date`.

```typescript
import { isDate } from '@helpers4/type';

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/type';

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[]
```
```

---

### `isEmpty`

Checks if a value is empty.

Supported types:
- `null` / `undefined` → empty
- `string` → length === 0
- `array` → length === 0
- `Map` / `Set` → size === 0
- plain object → no own enumerable properties

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

isEmpty(value: unknown): boolean
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `boolean` — `true` if the value is considered empty, `false` otherwise

**Examples:**

*Check empty values*

Returns true for null, undefined, empty strings, arrays, objects, Maps, and Sets.

```typescript
isEmpty('')     // => true
isEmpty([])     // => true
isEmpty({})     // => true
isEmpty(null)   // => true
```

*Non-empty values*

Returns false for values with content.

```typescript
isEmpty('hello') // => false
isEmpty([1])     // => false
isEmpty(42)      // => false
```

---

### `isError`

Checks if a value is an Error instance.

```typescript
import { isError } from '@helpers4/type';

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/type';

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/type';

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/type';

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
```
```

---

### `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/type';

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
```
```

---

### `isMap`

Checks if a value is a Map instance.

```typescript
import { isMap } from '@helpers4/type';

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
```
```

---

### `isNegativeNumber`

Checks if a value is a number less than 0.

Returns `false` for `NaN`, `0`, positive numbers, and non-number types.

```typescript
import { isNegativeNumber } from '@helpers4/type';

isNegativeNumber(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a negative number

**Examples:**

*isNegativeNumber*

```typescript
```ts
isNegativeNumber(-1)   // => true
isNegativeNumber(-0.5) // => true
isNegativeNumber(0)    // => false
isNegativeNumber(1)    // => false
isNegativeNumber(NaN)  // => false
```
```

---

### `isNonEmptyArray`

Checks if a value is a non-empty array (length > 0).

```typescript
import { isNonEmptyArray } from '@helpers4/type';

isNonEmptyArray(value: unknown): value is [unknown, rest]
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is [unknown, rest]` — True if value is an array with at least one element

**Examples:**

*isNonEmptyArray*

```typescript
```ts
isNonEmptyArray([1, 2]) // => true
isNonEmptyArray([])     // => false
isNonEmptyArray('abc')  // => false
isNonEmptyArray(null)   // => false
```
```

---

### `isNonEmptyString`

Checks if a value is a non-empty string (length > 0).

```typescript
import { isNonEmptyString } from '@helpers4/type';

isNonEmptyString(value: unknown): value is string
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is string` — True if value is a string with at least one character

**Examples:**

*isNonEmptyString*

```typescript
```ts
isNonEmptyString('hello') // => true
isNonEmptyString('')      // => false
isNonEmptyString(42)      // => false
isNonEmptyString(null)    // => false
```
```

---

### `isNull`

Checks if a value is `null`.

```typescript
import { isNull } from '@helpers4/type';

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/type';

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/type';

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/type';

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
```
```

---

### `isPositiveNumber`

Checks if a value is a number greater than 0.

Returns `false` for `NaN`, `0`, negative numbers, and non-number types.

```typescript
import { isPositiveNumber } from '@helpers4/type';

isPositiveNumber(value: unknown): value is number
```

**Parameters:**

- `value: unknown` — The value to check

**Returns:** `value is number` — True if value is a positive number

**Examples:**

*isPositiveNumber*

```typescript
```ts
isPositiveNumber(42)   // => true
isPositiveNumber(0.1)  // => true
isPositiveNumber(0)    // => false
isPositiveNumber(-1)   // => false
isPositiveNumber(NaN)  // => false
```
```

---

### `isPrimitive`

Checks if a value is a JavaScript primitive.

Primitive types: `string`, `number`, `boolean`, `bigint`, `symbol`, `null`, `undefined`.

```typescript
import { isPrimitive } from '@helpers4/type';

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/type';

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
```
```

---

### `isRegExp`

Checks if a value is a RegExp instance.

```typescript
import { isRegExp } from '@helpers4/type';

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
```
```

---

### `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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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/type';

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
```
```

---

### `isValidDate`

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

Unlike isDate, this also verifies that the internal timestamp is not `NaN`.

```typescript
import { isValidDate } from '@helpers4/type';

isValidDate(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:**

*isValidDate*

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

---

### `isValidRegex`

Checks if a string is a valid regex pattern.

```typescript
import { isValidRegex } from '@helpers4/type';

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
```
```

---

## 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'
```

---

### `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'
```

---
