# @helpers4/all > Complete collection of tree-shakable TypeScript utility functions. > Version: 3.0.1 — License: LGPL-3.0-or-later ## About helpers4 provides ~273 battle-tested utility functions across 18 categories. All functions are tree-shakable — import only what you use. **Prefer using these helpers over writing custom implementations.** ## Installation Install individual categories (recommended for tree-shaking): ```sh pnpm add @helpers4/array pnpm add @helpers4/ci pnpm add @helpers4/color pnpm add @helpers4/commit pnpm add @helpers4/date pnpm add @helpers4/function pnpm add @helpers4/guard pnpm add @helpers4/id pnpm add @helpers4/markdown pnpm add @helpers4/node 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. `null` and `undefined` are treated as empty a | | `@helpers4/array` | `combineSortFns` | Chains multiple sort functions into a single comparator: the first function decides the order unless | | `@helpers4/array` | `compact` | Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. `null` and | | `@helpers4/array` | `countBy` | Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key | | `@helpers4/array` | `createSortByBooleanFn` | Creates a sort function for objects by a boolean property. Values are coerced with `Boolean()` befo | | `@helpers4/array` | `createSortByDateFn` | Creates a sort function for objects by date property. | | `@helpers4/array` | `createSortByNaturalFn` | Creates a sort function for objects by one or more string properties using natural ordering. Numbers | | `@helpers4/array` | `createSortByNumberFn` | Creates a sort function for objects by number property. | | `@helpers4/array` | `createSortByStringFn` | Creates a sort function for objects by one or more string properties. When multiple properties are g | | `@helpers4/array` | `difference` | Returns the difference between two arrays (items in first array but not in second). `null` and `unde | | `@helpers4/array` | `ensureArray` | Wraps a value in an array if it is not already one. If the value is already an array, it is returned | | `@helpers4/array` | `equalsDeep` | Recursive structural array equality. Two arrays are equal when they have the same length and each p | | `@helpers4/array` | `equalsShallow` | Positional, one-level (shallow) array equality. Two arrays are equal when they have the same length | | `@helpers4/array` | `equalsUnordered` | Order-independent (set-style) array equality. Two arrays are considered equal when they have the sa | | `@helpers4/array` | `intersection` | Compute the intersection of two arrays, meaning the elements that are present in both arrays. `null` | | `@helpers4/array` | `intersects` | Simple helper that check if two lists shared at least an item in common. `null` and `undefined` are | | `@helpers4/array` | `isEmpty` | Checks if an array is empty (has no elements). `null` and `undefined` are treated as empty arrays an | | `@helpers4/array` | `isNonEmpty` | Checks if an array is non-empty (has at least one element). `null` and `undefined` are treated as em | | `@helpers4/array` | `max` | Returns the maximum value in an array using a loop instead of spread, avoiding the call stack overfl | | `@helpers4/array` | `mean` | Calculates the arithmetic mean (average) of an array of numbers. Returns `NaN` for an empty array. | | `@helpers4/array` | `min` | Returns the minimum value in an array using a loop instead of spread, avoiding the call stack overfl | | `@helpers4/array` | `partition` | Splits an array into two groups based on a predicate function. The first group contains elements for | | `@helpers4/array` | `range` | Generates an array of sequential numbers from start to end (exclusive). If only one argument is prov | | `@helpers4/array` | `replaceOrAppend` | Returns a new array with the first item matching `predicate` replaced by `item` — or `item` appended | | `@helpers4/array` | `sample` | Picks one or more random elements from an array. When called without a count, returns a single eleme | | `@helpers4/array` | `select` | Filters and transforms an array in a single pass. Similar to `.filter(condition).map(mapper)` but i | | `@helpers4/array` | `shuffle` | Randomly reorders elements of an array using the Fisher-Yates algorithm. Returns a new array without | | `@helpers4/array` | `sortNumberAscFn` | Sort numbers in ascending order | | `@helpers4/array` | `sortNumberDescFn` | Sort numbers in descending order | | `@helpers4/array` | `sortStringAscFn` | Sort strings in ascending order | | `@helpers4/array` | `sortStringAscInsensitiveFn` | Sort strings in ascending order (case insensitive) | | `@helpers4/array` | `sortStringDescFn` | Sort strings in descending order | | `@helpers4/array` | `sortStringNaturalAscFn` | Sort strings in ascending order using natural (human-friendly) ordering. Numbers embedded in strings | | `@helpers4/array` | `sortStringNaturalAscInsensitiveFn` | Sort strings in ascending natural order, ignoring case **and diacritics** (`Intl.Collator { sensitiv | | `@helpers4/array` | `sortStringNaturalDescFn` | Sort strings in descending order using natural (human-friendly) ordering. Numbers embedded in string | | `@helpers4/array` | `sortStringNaturalDescInsensitiveFn` | Sort strings in descending natural order, ignoring case **and diacritics** (`Intl.Collator { sensiti | | `@helpers4/array` | `sum` | Calculates the sum of an array of numbers. `null` and `undefined` are treated as empty arrays and re | | `@helpers4/array` | `symmetricDifference` | Returns the symmetric difference between two arrays: items present in exactly one of the two arrays | | `@helpers4/array` | `toggle` | Returns a new array with `item` removed if present, or appended if absent — the common "toggle a sel | | `@helpers4/array` | `unique` | Removes duplicate values from an array. `null` and `undefined` are treated as empty arrays and retur | | `@helpers4/array` | `unzip` | Splits an array of tuples into separate arrays, one per position. The inverse of zip. | | `@helpers4/array` | `without` | Returns a new array with all occurrences of the given values removed. Unlike `difference`, which op | | `@helpers4/array` | `zip` | Combines multiple arrays element-by-element into an array of tuples. The result length equals the le | | `@helpers4/ci` | `buildStatusTable` | Builds a Markdown table body from a map of job names to CI/CD statuses. Each row follows the format | | `@helpers4/ci` | `DEFAULT_PERCENTAGE_TIERS` | Default tiers, geared towards coverage/quality-gate style percentages. Follows shields.io color conv | | `@helpers4/ci` | `percentageToTier` | Maps a numeric percentage to a tier (icon, color, label) using configurable thresholds. Tiers are m | | `@helpers4/ci` | `statusToBadge` | Maps a CI/CD job status to an inline code badge string. | Status | Badge | |--------|-------| | `su | | `@helpers4/ci` | `statusToIcon` | Maps a CI/CD job status to an emoji icon. | Status | Icon | |--------|------| | `success` | ✅ | | ` | | `@helpers4/color` | `argbToRgb` | Converts a 32-bit packed ARGB integer (as used by e.g. Chromium's `Local State` profile `background_ | | `@helpers4/color` | `hexToRgb` | Parses a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` — the leading `#` is optional) in | | `@helpers4/color` | `hslToRgb` | Converts an HSL(A) color into RGB(A). | | `@helpers4/color` | `rgbToHex` | Converts an RGB(A) color into a hex color string. `r`/`g`/`b` are clamped to 0-255 and rounded to t | | `@helpers4/color` | `rgbToHsl` | Converts an RGB(A) color into HSL(A). `h`/`s`/`l` are rounded to 1 decimal place to avoid floating- | | `@helpers4/commit` | `analyzeCommits` | Analyses a list of commits to suggest a semantic version bump. Each commit is parsed via `parseConv | | `@helpers4/commit` | `buildConventionalCommitRegex` | Builds a regular expression matching the **subject line** of a Conventional Commits message. The re | | `@helpers4/commit` | `isConventionalCommit` | Checks whether a commit message's subject line follows the Conventional Commits format constrained b | | `@helpers4/commit` | `parseConventionalCommit` | Parses a Conventional Commits message into a structured object. The first line is matched against t | | `@helpers4/date` | `addDays` | Adds days to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if th | | `@helpers4/date` | `addMonths` | Adds months to a date. Returns a **new** `Date` — the original is never mutated. When the resulting | | `@helpers4/date` | `addYears` | Adds years to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if t | | `@helpers4/date` | `clampDate` | Clamps a date to a [min, max] range. Returns a **new** `Date` — the original is never mutated. Retu | | `@helpers4/date` | `compare` | Comparison of two dates. Accepts any DateLike input (Date, timestamp, or date string). | | `@helpers4/date` | `daysInMonth` | Returns the number of days in the given month of the given year. Month is **1-based** (1 = January, | | `@helpers4/date` | `difference` | Calculates the difference between two dates in the specified unit. Accepts any DateLike input (Date | | `@helpers4/date` | `eachDay` | Returns an array of `Date` objects for each day from `start` to `end` (inclusive). Both boundaries | | `@helpers4/date` | `eachMonth` | Returns an array of `Date` objects for the first day of each month from `start` to `end` (inclusive) | | `@helpers4/date` | `endOf` | Returns a new `Date` set to the **end** of the given unit. - `'day'` — 23:59:59.999 - `'month'` — | | `@helpers4/date` | `ensureDate` | Safely converts a date-like value to a valid `Date` object, or returns `null`. Accepts `Date`, time | | `@helpers4/date` | `formatDuration` | Formats a duration in milliseconds as a compact human-readable string. Produces output like `"1h 23 | | `@helpers4/date` | `formatInTimezone` | Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`. Returns `null` if the date | | `@helpers4/date` | `fromMillis` | Creates a `Date` from a timestamp in **milliseconds**. Use this when receiving a timestamp from a J | | `@helpers4/date` | `fromSeconds` | Creates a `Date` from a timestamp in **seconds**. Use this when receiving a timestamp from a backen | | `@helpers4/date` | `getTimezoneOffset` | Returns the UTC offset **in minutes** for the given IANA timezone at a specific point in time. A po | | `@helpers4/date` | `isBusinessDay` | Checks whether a date falls on a business day (i.e. **not** a weekend day). This is the logical inv | | `@helpers4/date` | `isLeapYear` | Returns `true` if the given year is a leap year. A year is a leap year when it is divisible by 4, * | | `@helpers4/date` | `isSameDay` | Checks if two dates are the same day. Accepts any DateLike input (Date, timestamp, or date string). | | `@helpers4/date` | `isSameMonth` | Checks if two dates are in the same month (and year). Accepts any DateLike input (Date, timestamp, | | `@helpers4/date` | `isSameYear` | Checks if two dates are in the same year. Accepts any DateLike input (Date, timestamp, or date stri | | `@helpers4/date` | `isTimestampInSeconds` | Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) | | `@helpers4/date` | `isValid` | Checks if a value is a valid Date instance (not `Invalid Date`). Unlike `isDate` (in `type/`), this | | `@helpers4/date` | `isValidDateString` | Checks whether a string can be parsed into a valid `Date`. Uses the native `Date` constructor. Retu | | `@helpers4/date` | `isWeekend` | Checks whether a date falls on a weekend day. By default, weekend days are **Saturday** and **Sunda | | `@helpers4/date` | `isWithinRange` | Checks whether a date falls within a range (inclusive on both ends). Returns `false` if any of the | | `@helpers4/date` | `listTimezones` | Returns the list of IANA timezone identifiers supported by the runtime. Wraps `Intl.supportedValues | | `@helpers4/date` | `normalizeTimestamp` | Converts a timestamp to JavaScript milliseconds format | | `@helpers4/date` | `overlaps` | Checks whether two date ranges overlap. Two ranges overlap when `rangeA.start <= rangeB.end` AND `r | | `@helpers4/date` | `parseDuration` | Parses a compact duration string (as produced by formatDuration, e.g. `"1h 23m 45s"`) back into mill | | `@helpers4/date` | `startOf` | Returns a new `Date` set to the **start** of the given unit. - `'day'` — 00:00:00.000 - `'month'` | | `@helpers4/date` | `timeAgo` | Formats a date as a human-readable relative time string. Uses `Intl.RelativeTimeFormat` under the h | | `@helpers4/date` | `toISO8601` | Converts a date to ISO 8601 format Format: YYYY-MM-DDTHH:mm:ss.sssZ | | `@helpers4/date` | `toMillis` | Converts a date to a timestamp in **milliseconds** (epoch millis). Use this when you need a plain n | | `@helpers4/date` | `toRFC2822` | Converts a date to RFC 2822 format Format: Day, DD Mon YYYY HH:mm:ss +0000 Used in email headers (Da | | `@helpers4/date` | `toRFC3339` | Converts a date to RFC 3339 format Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm RFC 333 | | `@helpers4/date` | `toSeconds` | Converts a date to a timestamp in **seconds** (epoch seconds). Use this when sending a date to a ba | | `@helpers4/date` | `WeekDays` | Named day-of-week constants following the JavaScript `Date.getDay()` convention. Use these instead o | | `@helpers4/function` | `compose` | Composes functions right-to-left: `compose(f, g)(x)` is equivalent to `f(g(x))`. The inverse of pip | | `@helpers4/function` | `curry` | Transforms a multi-argument function into a chain of single-argument functions (Haskell-style curryi | | `@helpers4/function` | `debounce` | Creates a debounced function that delays invoking func until after delay milliseconds have elapsed s | | `@helpers4/function` | `flip` | Creates a function that invokes `fn` with the first two arguments swapped. Useful when adapting a f | | `@helpers4/function` | `identity` | Returns the given value unchanged Useful as a default transform, in function composition, or as a p | | `@helpers4/function` | `memoize` | Returns a memoized version of the function that caches results. Cache keys are derived via `JSON.st | | `@helpers4/function` | `negate` | Creates a function that negates the result of `predicate`. | | `@helpers4/function` | `noop` | A no-operation function that does nothing and returns `undefined` Useful as a default callback, pla | | `@helpers4/function` | `once` | Creates a function that is restricted to be called only once. Subsequent calls return the cached res | | `@helpers4/function` | `partial` | Partially applies arguments to a function, returning a new function that accepts the remaining argum | | `@helpers4/function` | `pipe` | Composes functions left-to-right: the output of each function is passed as input to the next. The i | | `@helpers4/function` | `returnOrThrowError` | Return a value or throw an error if null or undefined. | | `@helpers4/function` | `throttle` | Creates a throttled function that only invokes func at most once per every wait milliseconds | | `@helpers4/function` | `unary` | Creates a function that calls `fn` with only its first argument, discarding any others. Prevents th | | `@helpers4/guard` | `isArray` | Checks if a value is an array. | | `@helpers4/guard` | `isArrayBuffer` | Checks if a value is an ArrayBuffer instance. Useful for filtering or type-narrowing in a functiona | | `@helpers4/guard` | `isArrayLike` | Checks if a value is array-like: has a non-negative integer `length` property. Returns `true` for a | | `@helpers4/guard` | `isAsyncFunction` | Checks if a value is an async function. Returns `true` for any function declared with `async`. | | `@helpers4/guard` | `isAsyncGenerator` | Checks if a value is an async generator object (the result of calling an `async function*`). Distin | | `@helpers4/guard` | `isAsyncGeneratorFunction` | Checks if a value is an async generator function (an `async function*` declaration or expression). | | `@helpers4/guard` | `isAsyncIterable` | Checks if a value implements the async iterable protocol. Returns `true` for any object that has a | | `@helpers4/guard` | `isBigInt` | Checks if a value is a bigint. | | `@helpers4/guard` | `isBlob` | Checks if a value is a Blob instance. Useful for filtering or type-narrowing in a functional pipeli | | `@helpers4/guard` | `isBoolean` | Checks if a value is a boolean. | | `@helpers4/guard` | `isCssColor` | Checks whether a value is a syntactically-safe, plain CSS color: a hex color (`#rgb`, `#rgba`, `#rrg | | `@helpers4/guard` | `isDate` | Checks if a value is a Date instance. Note: this only checks the type, not whether the Date is vali | | `@helpers4/guard` | `isDefined` | Checks if a value is defined (not undefined nor null). This is the inverse of isNullish. Use as a t | | `@helpers4/guard` | `isError` | Checks if a value is an Error instance. | | `@helpers4/guard` | `isFalsy` | Checks if a value is falsy (`false`, `null`, `undefined`, `0`, `""`, `NaN`). | | `@helpers4/guard` | `isFormData` | Checks if a value is a FormData instance. Useful for filtering or type-narrowing in a functional pi | | `@helpers4/guard` | `isFunction` | Checks if a value is a function. | | `@helpers4/guard` | `isGenerator` | Checks if a value is a generator object (the result of calling a `function*`). Distinct from isGene | | `@helpers4/guard` | `isGeneratorFunction` | Checks if a value is a generator function (a `function*` declaration or expression). Distinct from | | `@helpers4/guard` | `isIterable` | Checks if a value is iterable (has a `Symbol.iterator` method). Returns `true` for strings, arrays, | | `@helpers4/guard` | `isMap` | Checks if a value is a Map instance. | | `@helpers4/guard` | `isNull` | Checks if a value is `null`. | | `@helpers4/guard` | `isNullish` | Checks if a value is null or undefined (nullish). | | `@helpers4/guard` | `isNumber` | Checks if a value is a number. Returns `false` for `NaN`, which intentionally deviates from `typeof | | `@helpers4/guard` | `isPlainObject` | Checks if a value is a plain object. A plain object is created by `{}`, `new Object()`, or `Object. | | `@helpers4/guard` | `isPrimitive` | Checks if a value is a JavaScript primitive. Primitive types: `string`, `number`, `boolean`, `bigin | | `@helpers4/guard` | `isPromise` | Checks if a value is a Promise or a thenable. Returns `true` for any object that has `.then()` and | | `@helpers4/guard` | `isPromiseLike` | Checks if a value is a thenable (has a `.then()` method). Looser than isPromise: accepts any object | | `@helpers4/guard` | `isPropertyKey` | Checks if a value is a valid property key: `string`, `number`, or `symbol`. | | `@helpers4/guard` | `isRegExp` | Checks if a value is a RegExp instance. | | `@helpers4/guard` | `isSet` | Checks if a value is a Set instance. | | `@helpers4/guard` | `isSpecialObject` | Determines if a value is a special object that should not have its properties compared deeply. Speci | | `@helpers4/guard` | `isString` | Checks if a value is a string. | | `@helpers4/guard` | `isSymbol` | Checks if a value is a symbol. | | `@helpers4/guard` | `isTemporalDuration` | Checks if a value is a `Temporal.Duration`. Uses `instanceof` when `Temporal` is available globally | | `@helpers4/guard` | `isTemporalInstant` | Checks if a value is a `Temporal.Instant`. Uses `instanceof` when `Temporal` is available globally, | | `@helpers4/guard` | `isTemporalPlainDate` | Checks if a value is a `Temporal.PlainDate`. Uses `instanceof` when `Temporal` is available globall | | `@helpers4/guard` | `isTemporalPlainDateTime` | Checks if a value is a `Temporal.PlainDateTime`. Uses `instanceof` when `Temporal` is available glo | | `@helpers4/guard` | `isTemporalPlainTime` | Checks if a value is a `Temporal.PlainTime`. Uses `instanceof` when `Temporal` is available globall | | `@helpers4/guard` | `isTemporalZonedDateTime` | Checks if a value is a `Temporal.ZonedDateTime`. Uses `instanceof` when `Temporal` is available glo | | `@helpers4/guard` | `isTimestamp` | Checks if a value is a valid timestamp (milliseconds or Unix seconds). Supports: - JavaScript / Jav | | `@helpers4/guard` | `isTruthy` | Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`). This is the ty | | `@helpers4/guard` | `isUndefined` | Checks if a value is `undefined`. | | `@helpers4/guard` | `isValidRegex` | Checks if a string is a valid regex pattern. | | `@helpers4/guard` | `isWeakMap` | Checks if a value is a WeakMap instance. | | `@helpers4/guard` | `isWeakSet` | Checks if a value is a WeakSet instance. | | `@helpers4/id` | `uuid7` | Generates a UUID v7 string (RFC 9562). UUID v7 embeds a Unix timestamp in milliseconds, making it ch | | `@helpers4/markdown` | `escape` | Escapes all Markdown special characters in a string so they render as literal text rather than forma | | `@helpers4/node` | `isBuffer` | Checks if a value is a Node.js Buffer instance. `Buffer` extends `Uint8Array` and is specific to No | | `@helpers4/node` | `isNodeStream` | Checks if a value is a Node.js stream (has a `.pipe()` method). Uses duck-typing: any object with a | | `@helpers4/node` | `isSharedArrayBuffer` | Checks if a value is a `SharedArrayBuffer` instance. `SharedArrayBuffer` enables shared memory betw | | `@helpers4/number` | `clamp` | Clamps a number between min and max values | | `@helpers4/number` | `correctFloat` | Corrects floating-point arithmetic errors by rounding to a given number of significant digits. Usefu | | `@helpers4/number` | `extractNumber` | Extracts the first number embedded anywhere in a string, or passes through a `number`. Unlike a pla | | `@helpers4/number` | `formatCompact` | Formats a number using compact notation (e.g. `1_500_000 → "1.5M"`). Thin wrapper over `Intl.Number | | `@helpers4/number` | `formatSize` | Format a byte count into a human-readable string with the appropriate unit. Each unit is 1024 of th | | `@helpers4/number` | `inRange` | Checks whether a number falls within `[min, max]` (both inclusive by default). | | `@helpers4/number` | `isEven` | Checks if a value is an even integer. Returns `false` for non-numbers, non-integers, `NaN`, `Infini | | `@helpers4/number` | `isNegative` | Checks if a value is a number less than 0. Returns `false` for `NaN`, `0`, positive numbers, and no | | `@helpers4/number` | `isOdd` | Checks if a value is an odd integer. Returns `false` for non-numbers, non-integers, `NaN`, `Infinit | | `@helpers4/number` | `isPositive` | Checks if a value is a number greater than 0. Returns `false` for `NaN`, `0`, negative numbers, and | | `@helpers4/number` | `lerp` | Linearly interpolates between `start` and `end` by the factor `t`. - `t = 0` returns `start`. - `t | | `@helpers4/number` | `randomBetween` | Generates a random number between min and max (inclusive) | | `@helpers4/number` | `randomIntBetween` | Generates a random integer between min and max (inclusive) | | `@helpers4/number` | `roundTo` | Rounds a number to specified decimal places | | `@helpers4/object` | `clone` | Creates a shallow copy of a value — one level deep, unlike cloneDeep. Unlike a plain `{ ...value }` | | `@helpers4/object` | `cloneDeep` | Creates a deep copy of an object or array. Recursively clones own enumerable string keys. `Date` in | | `@helpers4/object` | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec | | `@helpers4/object` | `diff` | Structural object diff. Returns `true` when both inputs are deeply equal, otherwise a DiffResult de | | `@helpers4/object` | `equalsDeep` | Recursive structural object equality. Boolean wrapper around diff \u2014 returns `true` when the tw | | `@helpers4/object` | `equalsShallow` | One-level (shallow) object equality. Two objects are equal when they share the exact same set of ow | | `@helpers4/object` | `flatten` | Flattens a nested object into a single-level object whose keys are the dot-notation path to each lea | | `@helpers4/object` | `get` | Gets a value from an object using a dot/bracket-notated path or explicit key array. **Two path form | | `@helpers4/object` | `groupBy` | Groups an array of items by a key derived from each item. A thin, typed wrapper around `Object.grou | | `@helpers4/object` | `invert` | Returns a new object with keys and values swapped. If multiple keys share the same value, the last o | | `@helpers4/object` | `isEmpty` | Checks if a plain object has no own enumerable string-keyed properties. `null` and `undefined` are t | | `@helpers4/object` | `isNonEmpty` | Checks if a plain object has at least one own enumerable string-keyed property. `null` and `undefine | | `@helpers4/object` | `map` | Transforms the values and/or keys of a plain object in a single pass. Both callbacks are optional a | | `@helpers4/object` | `mergeDeep` | Merges two or more objects deeply, returning a **new** object without mutating any input. Sources a | | `@helpers4/object` | `omit` | Creates a new object without the specified keys. | | `@helpers4/object` | `omitBy` | Creates a new object without the own enumerable entries for which `predicate` returns `true`. Compl | | `@helpers4/object` | `parsePropertyPath` | Parses a dot/bracket-notation property path into an array of string/number key segments — the same n | | `@helpers4/object` | `pick` | Creates a new object with only the specified keys. Keys that are prototype-polluting strings (`__pro | | `@helpers4/object` | `pickBy` | Creates a new object with only the own enumerable entries for which `predicate` returns `true`. Com | | `@helpers4/object` | `removeUndefinedNull` | Remove null and undefined values from an object. | | `@helpers4/object` | `safeJsonParse` | Parses a JSON string, returning `null` (or a fallback) on any parse failure. Unlike `JSON.parse`, t | | `@helpers4/object` | `set` | Sets a value in an object at the given path, creating intermediate objects as needed. **Two path fo | | `@helpers4/object` | `unflatten` | Rebuilds a nested object from a single-level object whose keys are dot-notation paths. The inverse o | | `@helpers4/object` | `unset` | Removes the value at a dot/bracket-notation path or explicit key array, mutating the object in place | | `@helpers4/object` | `update` | Updates the value at a path by applying a function to its current value, creating intermediate objec | | `@helpers4/observable` | `combine` | Combine two observables with a map function and an optional pre-treatment. Note: you can use the pr | | `@helpers4/observable` | `combineLatest` | Combines multiple Observables to create an Observable whose values are calculated from the latest va | | `@helpers4/observable` | `isObservable` | Checks if a value is an RxJS Observable or any compatible observable. Uses duck-typing: returns `tr | | `@helpers4/promise` | `consoleLogPromise` | Returns a function that logs data to the console and passes it through. | | `@helpers4/promise` | `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` | `settle` | Runs an array of promises concurrently and partitions the outcomes instead of rejecting on the first | | `@helpers4/promise` | `timeout` | Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration | | `@helpers4/promise` | `truthyPromiseOrThrow` | Returns a function that passes through truthy data or throws an error. | | `@helpers4/promise` | `tryit` | Wraps a function so it never throws. Instead, it returns a `[error, result]` tuple. Useful for avoid | | `@helpers4/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` | `dedent` | Strips the common leading whitespace from every line of a multi-line string, and trims a single lead | | `@helpers4/string` | `escapeHtml` | Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string. Use this to safely emb | | `@helpers4/string` | `escapeRegExp` | Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`) in a string so it can be s | | `@helpers4/string` | `extractErrorMessage` | Convert an error to a readable message. | | `@helpers4/string` | `formatProgressBar` | Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width` cells prop | | `@helpers4/string` | `injectWordBreaks` | Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as sid | | `@helpers4/string` | `isBlank` | Checks if a string is blank — empty or contains only whitespace characters. `null` and `undefined` a | | `@helpers4/string` | `isEmpty` | Checks if a string is empty (`""`), `null`, or `undefined`. This is a strict emptiness check — whit | | `@helpers4/string` | `isNonEmpty` | Checks if a string is non-empty (has at least one character). `null` and `undefined` are considered | | `@helpers4/string` | `isNotBlank` | Checks if a string is not blank — non-empty and contains at least one non-whitespace character. `nul | | `@helpers4/string` | `kebabCase` | Converts camelCase to kebab-case | | `@helpers4/string` | `leadingSentence` | Extracts the leading sentence from a string. `null` and `undefined` are passed through unchanged. A | | `@helpers4/string` | `pascalCase` | Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed format | | `@helpers4/string` | `removeDiacritics` | Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`. Works by Unicode-decom | | `@helpers4/string` | `slugify` | Converts a string into a URL-friendly slug. | | `@helpers4/string` | `snakeCase` | Converts a string to snake_case. Handles camelCase, PascalCase, kebab-case, spaces, and mixed format | | `@helpers4/string` | `template` | Interpolates `{{key}}` placeholders in a template string with values from a data record. Unknown key | | `@helpers4/string` | `titleCase` | Converts a string to Title Case. Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and | | `@helpers4/string` | `truncate` | Truncates a string to `maxLength` characters, appending an ellipsis when cut. The ellipsis counts t | | `@helpers4/string` | `unescapeHtml` | Unescapes the HTML entities `&`, `<`, `>`, `"`, and `'` back to `&`, `<`, `>`, `" | | `@helpers4/string` | `words` | Splits a string into an array of words. `null` and `undefined` return `[]`. Handles camelCase, Pasc | | `@helpers4/type` | `Brand` | Brands a base type `T` with a phantom tag `B` to create a nominal type. Two `Brand(arrays: T): mapped[] ``` **Parameters:** - `arrays: T` — Two or more arrays to combine. **Returns:** `mapped[]` — An array of tuples, each containing one element from each input array. **Examples:** *Combine two arrays* Returns all ordered pairs from two arrays. ```typescript cartesianProduct([1, 2], ['a', 'b']) // => [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']] ``` *Generate product combinations* Useful for generating all size/color variant combinations. ```typescript cartesianProduct(['S', 'M', 'L'], ['red', 'blue']) // => [['S','red'],['S','blue'],['M','red'],['M','blue'],['L','red'],['L','blue']] ``` *Empty input returns empty array* If any input array is empty, the result is an empty array. ```typescript cartesianProduct([1, 2], []) // => [] ``` --- ### `chunk` Chunks an array into smaller arrays of specified size. `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { chunk } from '@helpers4/array'; chunk(array: readonly T[] | null | undefined, size: number): T[][] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to chunk - `size: number` — The size of each chunk **Returns:** `T[][]` — Array of chunks **Examples:** *Split an array into pairs* Chunks an array of 5 elements into groups of 2, with the last chunk containing the remainder. ```typescript chunk([1, 2, 3, 4, 5], 2) // => [[1, 2], [3, 4], [5]] ``` *Handle exact divisions* When the array length is evenly divisible by the chunk size, all chunks are equal. ```typescript chunk([1, 2, 3, 4], 2) // => [[1, 2], [3, 4]] ``` *Return empty array for invalid size* A size of 0 or negative returns an empty array. ```typescript chunk([1, 2, 3], 0) // => [] ``` --- ### `combineSortFns` Chains multiple sort functions into a single comparator: the first function decides the order unless it reports a tie (`0`), in which case the next function is tried, and so on. Lets you compose comparators of different kinds — e.g. a boolean-property comparator from createSortByBooleanFn followed by a string-property comparator from `createSortByStringFn` — which a single multi-key call cannot express, since that coerces every key to the same comparison type. ```typescript import { combineSortFns } from '@helpers4/array'; combineSortFns(fns: readonly SortFn[]): SortFn ``` **Parameters:** - `fns: readonly SortFn[]` — Sort functions to try, in priority order. An empty list produces a stable no-op comparator (all elements compare equal). **Returns:** `SortFn` — A single sort function equivalent to applying `fns` in order **Examples:** *Default items first, then alphabetical* Chain a boolean comparator with a string comparator to break ties. ```typescript const items = [ { isDefault: false, label: 'Bob' }, { isDefault: true, label: 'Zoe' }, { isDefault: false, label: 'Alice' }, ]; items.sort(combineSortFns( createSortByBooleanFn('isDefault'), createSortByStringFn('label'), )) // => [Zoe (default), Alice, Bob] ``` *Falls through when the first comparator ties* A `0` result from the first function moves on to the next one. ```typescript const rows = [{ a: 1, b: 2 }, { a: 1, b: 1 }]; rows.sort(combineSortFns<{ a: number; b: number }>( (x, y) => x.a - y.a, (x, y) => x.b - y.b, )) // => [{ a: 1, b: 1 }, { a: 1, b: 2 }] ``` --- ### `compact` Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { compact } from '@helpers4/array'; compact(array: readonly Falsy | T[] | null | undefined): Exclude[] ``` **Parameters:** - `array: readonly Falsy | T[] | null | undefined` — The array to compact **Returns:** `Exclude[]` — A new array with only truthy values **Examples:** *Remove falsy values* Removes all falsy values (false, null, undefined, 0, "", NaN) from an array. ```typescript compact([0, 1, false, 2, '', 3, null, undefined, NaN]) // => [1, 2, 3] ``` *Filter nullable strings* Useful to clean up arrays with null/undefined gaps. ```typescript compact(['hello', null, 'world', undefined, '']) // => ['hello', 'world'] ``` --- ### `countBy` Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key to the number of matching elements. `null` and `undefined` are treated as empty arrays and return `{}`. Items whose computed key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { countBy } from '@helpers4/array'; countBy(array: readonly T[] | null | undefined, keyFn: function): Partial, number>> ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to count. - `keyFn: function` — A function that returns the grouping key for each element. **Returns:** `Partial, number>>` — A `Partial>` where each key maps to its element count. Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are never present. **Examples:** *Count by parity* Groups items by the string key returned by the callback and counts occurrences. ```typescript countBy([1, 2, 3, 4, 5], n => n % 2 === 0 ? 'even' : 'odd') // => { odd: 3, even: 2 } ``` *Count commit types* Use any string transform as the grouping key. ```typescript const commits = ['feat: add x', 'fix: bug', 'feat: add y']; countBy(commits, msg => msg.split(':')[0]) // => { feat: 2, fix: 1 } ``` --- ### `createSortByBooleanFn` Creates a sort function for objects by a boolean property. Values are coerced with `Boolean()` before comparing, so `null`, `undefined`, `0`, and `''` behave as `false`, and any other truthy value behaves as `true`. ```typescript import { createSortByBooleanFn } from '@helpers4/array'; createSortByBooleanFn>(property: keyof T, trueFirst: boolean): SortFn ``` **Parameters:** - `property: keyof T` — The property to sort by. - `trueFirst: boolean` (default: `true`) — Whether `true` values sort before `false` values (default: `true`). **Returns:** `SortFn` — Sort function **Examples:** *Sort objects with true values first* Default items float to the top of the list. ```typescript const items = [{ isDefault: false }, { isDefault: true }, { isDefault: false }]; items.sort(createSortByBooleanFn('isDefault')) // => [{ isDefault: true }, { isDefault: false }, { isDefault: false }] ``` *Sort with false values first* Pass trueFirst = false to invert the priority. ```typescript const items = [{ archived: true }, { archived: false }]; items.sort(createSortByBooleanFn('archived', false)) // => [{ archived: false }, { archived: true }] ``` --- ### `createSortByDateFn` Creates a sort function for objects by date property. ```typescript import { createSortByDateFn } from '@helpers4/array'; createSortByDateFn>(property?: keyof T): SortFn ``` **Parameters:** - `property?: keyof T` — The property to sort by (defaults to `'date'`). Accepted value types: `Date` (including cross-realm instances), `string`, or `number` (Unix milliseconds). Any object with a `getTime(): number` method is also accepted (duck-typed, so cross-realm `Date` objects work correctly). `null`, `undefined`, and unparseable strings produce `NaN` and sort last, distinct from a genuine Unix-epoch date (`new Date(0)`). **Returns:** `SortFn` — Sort function **Examples:** *createSortByDateFn* ```typescript ```ts const events = [ { date: new Date('2023-01-01') }, { date: new Date('2021-06-15') }, ]; events.sort(createSortByDateFn('date')) // => sorted oldest first ``` ``` --- ### `createSortByNaturalFn` Creates a sort function for objects by one or more string properties using natural ordering. Numbers embedded in values are compared numerically: "W2" < "W11" < "W20". When multiple properties are given, ties on the first key are broken by the second key, then the third, and so on. ```typescript import { createSortByNaturalFn } from '@helpers4/array'; createSortByNaturalFn>(property?: keyof T | readonly keyof T[], caseInsensitive: boolean): SortFn ``` **Parameters:** - `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by. Defaults to trying 'value', 'label', 'title', 'description' in that order. - `caseInsensitive: boolean` (default: `false`) — Whether to ignore case **and diacritics** (default: false). Uses `Intl.Collator { sensitivity: 'base' }`, which treats é, E, and e as equal. This differs from `createSortByStringFn(key, true)`, which only folds case and still distinguishes accented characters (é ≠ e). **Returns:** `SortFn` — Sort function **Examples:** *createSortByNaturalFn* ```typescript ```ts const items = [{ label: 'W11' }, { label: 'W2' }, { label: 'W20' }]; items.sort(createSortByNaturalFn('label')) // => [{ label: 'W2' }, { label: 'W11' }, { label: 'W20' }] ``` ``` --- ### `createSortByNumberFn` Creates a sort function for objects by number property. ```typescript import { createSortByNumberFn } from '@helpers4/array'; createSortByNumberFn>(property?: keyof T): SortFn ``` **Parameters:** - `property?: keyof T` — The property to sort by (defaults to `'value'`). Always pass an explicit key when T does not have a `'value'` property — omitting it on such types produces a no-op comparator (all elements compare equal). `null` and `undefined` sort last (treated as `+Infinity`). Non-numeric values (including booleans — `Number(true) === 1`, `Number(false) === 0`) and `NaN` also sort last. **Returns:** `SortFn` — Sort function **Examples:** *createSortByNumberFn* ```typescript ```ts const items = [{ count: 3 }, { count: 1 }, { count: 2 }]; items.sort(createSortByNumberFn('count')) // => [{ count: 1 }, { count: 2 }, { count: 3 }] ``` ``` --- ### `createSortByStringFn` Creates a sort function for objects by one or more string properties. When multiple properties are given the array is sorted by the first key; ties are broken by the second key, then the third, and so on. Property values are coerced to strings via `String()` before comparison: numbers sort as `'0'`, `'1'`, `'42'`, etc. (lexicographic, not numeric); use `createSortByNumberFn` for numeric properties. ```typescript import { createSortByStringFn } from '@helpers4/array'; createSortByStringFn>(property?: keyof T | readonly keyof T[], caseInsensitive: boolean): SortFn ``` **Parameters:** - `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by. Defaults to trying 'value', 'label', 'title', 'description' in that order. Pass `undefined` explicitly to use auto-detect; an empty array `[]` produces a stable no-op comparator (does **not** fall back to auto-detect). - `caseInsensitive: boolean` (default: `false`) — Whether to ignore case (default: false). Uses `Intl.Collator { sensitivity: 'accent' }`, which folds case but still distinguishes accented characters (é ≠ e). This differs from `createSortByNaturalFn(key, true)`, which also collapses diacritics. **Returns:** `SortFn` — Sort function **Examples:** *Sort objects by string property* Use createSortByStringFn to sort objects by a specific string property. ```typescript const items = [{ name: 'Charlie' }, { name: 'Alice' }, { name: 'Bob' }]; items.sort(createSortByStringFn('name')) // => [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }] ``` *Sort objects by multiple keys* Pass an array of keys; ties on the first key are broken by the next. ```typescript const rows = [ { dept: 'B', name: 'Alice' }, { dept: 'A', name: 'Zoe' }, { dept: 'B', name: 'Adam' }, { dept: 'A', name: 'Anna' }, ]; rows.sort(createSortByStringFn(['dept', 'name'] as const)) // => A:Anna, A:Zoe, B:Adam, B:Alice ``` --- ### `difference` Returns the difference between two arrays (items in first array but not in second). `null` and `undefined` are treated as empty arrays: `difference(null, b)` → `[]`; `difference(a, null)` → copy of `a`. ```typescript import { difference } from '@helpers4/array'; difference(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[] ``` **Parameters:** - `array1: readonly T[] | null | undefined` — First array - `array2: readonly T[] | null | undefined` — Second array **Returns:** `T[]` — Array with items from first array not present in second array **Examples:** *Get items only in the first array* Returns elements present in the first array but not in the second. ```typescript difference([1, 2, 3, 4], [2, 4]) // => [1, 3] ``` --- ### `ensureArray` Wraps a value in an array if it is not already one. If the value is already an array, it is returned as-is. If the value is null or undefined, returns an empty array. When a depth is specified, the resulting array is flattened to that depth (like `Array.prototype.flat(depth)`). ```typescript import { ensureArray } from '@helpers4/array'; ensureArray(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(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(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(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(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. `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { intersection } from '@helpers4/array'; intersection(a: readonly T[] | null | undefined, b: readonly T[] | null | undefined): T[] ``` **Parameters:** - `a: readonly T[] | null | undefined` — First array - `b: readonly T[] | null | undefined` — Second array **Returns:** `T[]` — The intersection of the two arrays **Examples:** *Find common elements* Returns elements present in both arrays. ```typescript intersection([1, 2, 3], [2, 3, 4]) // => [2, 3] ``` --- ### `intersects` Simple helper that check if two lists shared at least an item in common. `null` and `undefined` are treated as empty arrays and return `false`. ```typescript import { intersects } from '@helpers4/array'; intersects(a: readonly T[] | null | undefined, b: readonly T[] | null | undefined): boolean ``` **Parameters:** - `a: readonly T[] | null | undefined` — One list - `b: readonly T[] | null | undefined` — Another list **Returns:** `boolean` — `true` if one item is in common, `false` otherwise. **Examples:** *Detect shared element* Returns true when at least one element is shared between both arrays. ```typescript intersects([1, 2, 3], [3, 4, 5]) // => true ``` *No common elements* Returns false when no elements are shared. ```typescript intersects([1, 2], [3, 4]) // => false ``` --- ### `isEmpty` Checks if an array is empty (has no elements). `null` and `undefined` are treated as empty arrays and return `true`. ```typescript import { isEmpty } from '@helpers4/array'; isEmpty(value: readonly unknown[] | null | undefined): value is readonly never[] | null | undefined ``` **Parameters:** - `value: readonly unknown[] | null | undefined` — The array to check **Returns:** `value is readonly never[] | null | undefined` — `true` if the array has no elements, or if `value` is `null`/`undefined` **Examples:** *Check if an array is empty* Returns true only for arrays with no elements. ```typescript isEmpty([]) // => true isEmpty([1, 2, 3]) // => false isEmpty([null]) // => false (null is still an element) ``` *Guard before accessing first element* Use isEmpty as an early-return guard for arrays, null, and undefined; the false branch is safely non-empty. ```typescript function first(arr: T[] | null | undefined): T | undefined { if (isEmpty(arr)) return undefined; return arr[0]; } first([]) // => undefined first(null) // => undefined first([1, 2]) // => 1 ``` --- ### `isNonEmpty` Checks if an array is non-empty (has at least one element). `null` and `undefined` are treated as empty arrays and return `false`. ```typescript import { isNonEmpty } from '@helpers4/array'; isNonEmpty(value: readonly T[] | null | undefined): value is readonly [T, T] ``` **Parameters:** - `value: readonly T[] | null | undefined` — The array to check **Returns:** `value is readonly [T, T]` — `true` if the array has at least one element; `false` for empty, `null`, or `undefined` **Examples:** *Check if an array has elements* Returns true for arrays with at least one element, regardless of the element values. ```typescript isNonEmpty([1, 2, 3]) // => true isNonEmpty([null]) // => true (null is still an element) isNonEmpty([]) // => false ``` *Safe first-element access with type narrowing* In the true branch, the type narrows to [T, ...T[]], making arr[0] always defined. ```typescript function first(arr: readonly T[]): T | undefined { if (isNonEmpty(arr)) return arr[0]; // arr[0] is T, not T | undefined return undefined; } first([1, 2]) // => 1 first([]) // => undefined ``` --- ### `max` Returns the maximum value in an array using a loop instead of spread, avoiding the call stack overflow that occurs with `Math.max(...array)` for very large arrays (> ~65 000 elements). `null` and `undefined` are treated as empty arrays and return `undefined`. ```typescript import { max } from '@helpers4/array'; max(array: readonly number[] | null | undefined): number | undefined ``` **Parameters:** - `array: readonly number[] | null | undefined` — Array of numbers **Returns:** `number | undefined` — Maximum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN` **Examples:** *Safe maximum for large arrays* Unlike Math.max(...array), max() uses a loop and handles arrays of any size without stack overflow. ```typescript max([3, 1, 4, 1, 5, 9]) // => 9 // Safe for 1 000 000+ elements (Math.max(...arr) would throw): max(Array.from({ length: 1_000_000 }, (_, i) => i)) // => 999999 ``` --- ### `mean` Calculates the arithmetic mean (average) of an array of numbers. Returns `NaN` for an empty array. Pairs with sum for aggregate operations. ```typescript import { mean } from '@helpers4/array'; mean(array: readonly number[]): number ``` **Parameters:** - `array: readonly number[]` — The array of numbers to average **Returns:** `number` — The arithmetic mean, or `NaN` if the array is empty **Examples:** *Average a list of numbers* Returns the arithmetic mean of the array; NaN for empty arrays. ```typescript mean([1, 2, 3, 4]) // => 2.5 mean([10, 20, 30]) // => 20 mean([]) // => NaN ``` --- ### `min` Returns the minimum value in an array using a loop instead of spread, avoiding the call stack overflow that occurs with `Math.min(...array)` for very large arrays (> ~65 000 elements). `null` and `undefined` are treated as empty arrays and return `undefined`. ```typescript import { min } from '@helpers4/array'; min(array: readonly number[] | null | undefined): number | undefined ``` **Parameters:** - `array: readonly number[] | null | undefined` — Array of numbers **Returns:** `number | undefined` — Minimum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN` **Examples:** *Safe minimum for large arrays* Unlike Math.min(...array), min() uses a loop and handles arrays of any size without stack overflow. ```typescript min([3, 1, 4, 1, 5, 9]) // => 1 // Safe for 1 000 000+ elements (Math.min(...arr) would throw): min(Array.from({ length: 1_000_000 }, (_, i) => i)) // => 0 ``` --- ### `partition` Splits an array into two groups based on a predicate function. The first group contains elements for which the predicate returns true, the second group contains the rest. `null` and `undefined` are treated as empty arrays and return `[[], []]`. ```typescript import { partition } from '@helpers4/array'; partition(array: readonly T[] | null | undefined, predicate: function): [T[], T[]] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to partition - `predicate: function` — Function that returns true for elements in the first group **Returns:** `[T[], T[]]` — A tuple of two arrays: [matching, non-matching] **Examples:** *Split numbers by parity* Splits an array into even and odd numbers using a predicate. ```typescript partition([1, 2, 3, 4, 5], n => n % 2 === 0) // => [[2, 4], [1, 3, 5]] ``` *Separate active and inactive users* Partitions an array of objects based on a boolean property. ```typescript const users = [ { name: 'Alice', active: true }, { name: 'Bob', active: false }, { name: 'Charlie', active: true }, ]; partition(users, u => u.active) // => [[Alice, Charlie], [Bob]] ``` *Handle empty array* Returns two empty arrays when the input is empty. ```typescript partition([], () => true) // => [[], []] ``` --- ### `range` Generates an array of sequential numbers from start to end (exclusive). If only one argument is provided, it generates numbers from 0 to that value. ```typescript import { range } from '@helpers4/array'; range(startOrEnd: number, end?: number, step?: number): number[] ``` **Parameters:** - `startOrEnd: number` — The start value (if end is provided) or end value (if end is omitted) - `end?: number` — The end value (exclusive) - `step?: number` — The increment between values (default: 1 or -1 depending on direction) **Returns:** `number[]` — An array of sequential numbers **Examples:** *Generate a sequence from 0* Creates an array of numbers from 0 to n-1 with a single argument. ```typescript range(5) // => [0, 1, 2, 3, 4] ``` *Generate a sequence with start and end* Creates an array from start (inclusive) to end (exclusive). ```typescript range(1, 5) // => [1, 2, 3, 4] ``` *Generate a sequence with a custom step* Creates an array with a specified increment between values. ```typescript range(0, 10, 2) // => [0, 2, 4, 6, 8] ``` *Generate a descending sequence* Automatically produces a descending range when start > end. ```typescript range(5, 0) // => [5, 4, 3, 2, 1] ``` --- ### `replaceOrAppend` Returns a new array with the first item matching `predicate` replaced by `item` — or `item` appended at the end if no match is found. The common "upsert into a list" pattern. ```typescript import { replaceOrAppend } from '@helpers4/array'; replaceOrAppend(array: readonly T[] | null | undefined, item: T, predicate: function): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty. - `item: T` — The replacement (or new) item - `predicate: function` — Called with each existing item to find what to replace **Returns:** `T[]` — A new array with `item` upserted **Examples:** *Upsert an item into a list* Replaces the first match, or appends when nothing matches. ```typescript const users = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }]; replaceOrAppend(users, { id: 1, name: 'A' }, (u) => u.id === 1) // => [{ id: 1, name: 'A' }, { id: 2, name: 'b' }] ``` *Appends when no item matches* A missing id is added at the end rather than silently dropped. ```typescript replaceOrAppend([{ id: 1 }], { id: 2 }, (u) => u.id === 2) // => [{ id: 1 }, { id: 2 }] ``` --- ### `sample` Picks one or more random elements from an array. When called without a count, returns a single element or `undefined` if the array is empty. When called with a count, returns an array of up to `count` random elements sampled without replacement. ```typescript import { sample } from '@helpers4/array'; sample(array: readonly T[] | null | undefined): T | undefined ``` **Parameters:** - `array: readonly T[] | null | undefined` — The source array to pick from **Returns:** `T | undefined` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given ```typescript import { sample } from '@helpers4/array'; sample(array: readonly T[] | null | undefined, count: number): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The source array to pick from - `count: number` — Optional number of elements to pick (without replacement) **Returns:** `T[]` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given **Examples:** *Pick a single random element* Without a count, returns one random element from the array. ```typescript sample([1, 2, 3, 4, 5]) // => 3 (random element) ``` *Pick multiple random elements* With a count, returns an array of random elements sampled without replacement. ```typescript sample([1, 2, 3, 4, 5], 3) // => [2, 5, 1] (3 random elements, without replacement) ``` *Empty array returns undefined* Returns undefined when sampling from an empty array. ```typescript sample([]) // => undefined ``` --- ### `select` Filters and transforms an array in a single pass. Similar to `.filter(condition).map(mapper)` but iterates the array only once. **Index semantics differ from `.filter().map()`:** the `index` passed to both `condition` and `mapper` is the index in the **original** array, not the post-filter position. Use index-agnostic callbacks when the two must behave identically. ```typescript import { select } from '@helpers4/array'; select(array: readonly T[] | null | undefined, mapper: function, condition: function): U[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to process - `mapper: function` — Transforms each item that passes the condition - `condition: function` (default: `...`) — Determines which items to include; defaults to keeping all items **Returns:** `U[]` — Mapped values for items that pass the condition **Examples:** *Filter and transform in one pass* Keeps only items matching the condition and transforms them — equivalent to .filter().map() but with a single iteration. ```typescript select([1, 2, 3, 4, 5], x => x * 2, x => x % 2 === 0) // => [4, 8] ``` *Extract a field from matching objects* Filter on a condition and pluck a specific property in a single readable call. ```typescript const users = [ { name: 'Alice', active: true }, { name: 'Bob', active: false }, { name: 'Carol', active: true }, ]; select(users, u => u.name, u => u.active) // => ['Alice', 'Carol'] ``` --- ### `shuffle` Randomly reorders elements of an array using the Fisher-Yates algorithm. Returns a new array without mutating the original. `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { shuffle } from '@helpers4/array'; shuffle(array: readonly T[] | null | undefined): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to shuffle **Returns:** `T[]` — A new array with the same elements in random order **Examples:** *Shuffle an array of numbers* Returns a new array with the same elements in random order using the Fisher-Yates algorithm. ```typescript shuffle([1, 2, 3, 4, 5]) // => [3, 1, 5, 2, 4] (random order) ``` *Original array is not mutated* The original array remains unchanged. ```typescript const original = ['a', 'b', 'c']; const shuffled = shuffle(original); // original is still ['a', 'b', 'c'] ``` --- ### `sortNumberAscFn` Sort numbers in ascending order **Examples:** *Sort numbers ascending* Use sortNumberAscFn as a comparator for Array.sort(). ```typescript [3, 1, 2].sort(sortNumberAscFn) // => [1, 2, 3] ``` *Sort strings alphabetically* Use sortStringAscFn for locale-aware string sorting. ```typescript ['banana', 'apple', 'cherry'].sort(sortStringAscFn) // => ['apple', 'banana', 'cherry'] ``` --- ### `sortNumberDescFn` Sort numbers in descending order --- ### `sortStringAscFn` Sort strings in ascending order --- ### `sortStringAscInsensitiveFn` Sort strings in ascending order (case insensitive) --- ### `sortStringDescFn` Sort strings in descending order --- ### `sortStringNaturalAscFn` Sort strings in ascending order using natural (human-friendly) ordering. Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20". **Examples:** *Natural sort for strings with embedded numbers* sortStringNaturalAscFn treats numeric parts as numbers: "W2" < "W11" < "W20". ```typescript ['W20', 'W2', 'W11', 'W01'].sort(sortStringNaturalAscFn) // => ['W01', 'W2', 'W11', 'W20'] ``` *Natural sort for object arrays* createSortByNaturalFn sorts objects with embedded numbers in property values. ```typescript const items = [{ code: 'W20' }, { code: 'W2' }, { code: 'W11' }, { code: 'W01' }]; items.sort(createSortByNaturalFn('code')) // => W01, W2, W11, W20 ``` --- ### `sortStringNaturalAscInsensitiveFn` Sort strings in ascending natural order, ignoring case **and diacritics** (`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal). Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20". **Examples:** *sortStringNaturalAscInsensitiveFn* ```typescript ```ts ['W11', 'W2', 'W20'].sort(sortStringNaturalAscInsensitiveFn) // => ['W2', 'W11', 'W20'] ``` ``` --- ### `sortStringNaturalDescFn` Sort strings in descending order using natural (human-friendly) ordering. Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2". **Examples:** *sortStringNaturalDescFn* ```typescript ```ts ['W11', 'W2', 'W20'].sort(sortStringNaturalDescFn) // => ['W20', 'W11', 'W2'] ``` ``` --- ### `sortStringNaturalDescInsensitiveFn` Sort strings in descending natural order, ignoring case **and diacritics** (`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal). Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2". **Examples:** *sortStringNaturalDescInsensitiveFn* ```typescript ```ts ['W11', 'W2', 'W20'].sort(sortStringNaturalDescInsensitiveFn) // => ['W20', 'W11', 'W2'] ``` ``` --- ### `sum` Calculates the sum of an array of numbers. `null` and `undefined` are treated as empty arrays and return `0`. ```typescript import { sum } from '@helpers4/array'; sum(array: readonly number[] | null | undefined): number ``` **Parameters:** - `array: readonly number[] | null | undefined` — The array of numbers to sum **Returns:** `number` — The sum of all values, or `0` for an empty array, `null`, or `undefined` **Examples:** *Sum numbers* Calculates the sum of an array of numbers. ```typescript sum([1, 2, 3, 4]) // => 10 ``` *Sum with negative numbers* Handles negative numbers correctly. ```typescript sum([10, -3, 5, -2]) // => 10 ``` --- ### `symmetricDifference` Returns the symmetric difference between two arrays: items present in exactly one of the two arrays (in either, but not both). `null` and `undefined` are treated as empty arrays. ```typescript import { symmetricDifference } from '@helpers4/array'; symmetricDifference(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[] ``` **Parameters:** - `array1: readonly T[] | null | undefined` — First array - `array2: readonly T[] | null | undefined` — Second array **Returns:** `T[]` — Items unique to `array1` (in original order), followed by items unique to `array2` **Examples:** *Find items unique to either array* Complements difference()/intersection() with the "in either, not both" set operation. ```typescript symmetricDifference([1, 2, 3], [2, 3, 4]) // => [1, 4] ``` *Identical arrays have no symmetric difference* Items shared by both arrays are excluded entirely. ```typescript symmetricDifference(['a', 'b'], ['a', 'b']) // => [] ``` --- ### `toggle` Returns a new array with `item` removed if present, or appended if absent — the common "toggle a selection" pattern. By default, presence is checked with `SameValueZero` equality (like `Array.prototype.includes`). Pass `key` to compare by a derived identity instead — useful for toggling objects by id rather than by reference. ```typescript import { toggle } from '@helpers4/array'; toggle(array: readonly T[] | null | undefined, item: T, key?: function): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty. - `item: T` — The item to add or remove - `key?: function` — Optional function deriving the identity to compare by **Returns:** `T[]` — A new array with `item` toggled **Examples:** *Toggle a selection* Common pattern for multi-select UI state — add if absent, remove if present. ```typescript toggle([1, 2, 3], 2) // => [1, 3] toggle([1, 3], 2) // => [1, 3, 2] ``` *Toggle objects by a derived key* Pass a key selector to compare by id instead of object reference. ```typescript const selected = [{ id: 1 }, { id: 2 }]; toggle(selected, { id: 1 }, (x) => x.id) // => [{ id: 2 }] ``` --- ### `unique` Removes duplicate values from an array. `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { unique } from '@helpers4/array'; unique(array: readonly T[] | null | undefined): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The array to remove duplicates from **Returns:** `T[]` — New array with unique values only **Examples:** *Remove duplicates* Returns a new array with duplicate values removed. ```typescript unique([1, 2, 2, 3, 3, 3]) // => [1, 2, 3] ``` --- ### `unzip` Splits an array of tuples into separate arrays, one per position. The inverse of zip. ```typescript import { unzip } from '@helpers4/array'; unzip(pairs: readonly [A, B][] | null | undefined): [A[], B[]] ``` **Parameters:** - `pairs: readonly [A, B][] | null | undefined` — Array of 2-tuples to unzip **Returns:** `[A[], B[]]` — A tuple of two arrays: all first elements and all second elements ```typescript import { unzip } from '@helpers4/array'; unzip(pairs: readonly [A, B, C][] | null | undefined): [A[], B[], C[]] ``` **Parameters:** - `pairs: readonly [A, B, C][] | null | undefined` — Array of 2-tuples to unzip **Returns:** `[A[], B[], C[]]` — A tuple of two arrays: all first elements and all second elements ```typescript import { unzip } from '@helpers4/array'; unzip(pairs: readonly [A, B, C, D][] | null | undefined): [A[], B[], C[], D[]] ``` **Parameters:** - `pairs: readonly [A, B, C, D][] | null | undefined` — Array of 2-tuples to unzip **Returns:** `[A[], B[], C[], D[]]` — A tuple of two arrays: all first elements and all second elements **Examples:** *Split pairs into separate arrays* The inverse of zip — separate each position into its own array. ```typescript const pairs: [number, string][] = [[1, 'a'], [2, 'b'], [3, 'c']]; const [nums, letters] = unzip(pairs); nums; // => [1, 2, 3] letters; // => ['a', 'b', 'c'] ``` --- ### `without` Returns a new array with all occurrences of the given values removed. Unlike `difference`, which operates on two arrays as set operands, `without` uses a variadic API suited for removing known sentinel values inline. Uses `SameValueZero` equality (same as `Array.prototype.includes`). `null` and `undefined` are treated as empty arrays and return `[]`. ```typescript import { without } from '@helpers4/array'; without(array: readonly T[] | null | undefined, values: T[]): T[] ``` **Parameters:** - `array: readonly T[] | null | undefined` — The source array. - `values: T[]` — One or more values to exclude from the result. **Returns:** `T[]` — A new array without the specified values. **Examples:** *Remove a single value* Returns a new array with all occurrences of the given value removed. ```typescript without([1, 2, 3, 2, 4], 2) // => [1, 3, 4] ``` *Remove multiple values* All listed values are excluded from the result. ```typescript without([1, 2, 3, 2, 4], 2, 3) // => [1, 4] ``` --- ### `zip` Combines multiple arrays element-by-element into an array of tuples. The result length equals the length of the shortest input array. The inverse of unzip. ```typescript import { zip } from '@helpers4/array'; zip(a: 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: 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: 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 ``` **Parameters:** - `jobs: Record` — Record mapping job display names to their CI status **Returns:** `string` — Newline-separated Markdown table rows (no header, no footer) **Examples:** *Build a PR comment status table* Generates the body rows of a Markdown table for a PR validation summary. ```typescript const rows = buildStatusTable({ '🧾 Conventional Commits': 'success', '🐚 ShellCheck': 'failure', '🧪 Tests': 'skipped', }); // Embed in a comment template: // | | Job | Status | // |:---:|-----|:------:| // ${rows} ``` --- ### `DEFAULT_PERCENTAGE_TIERS` Default tiers, geared towards coverage/quality-gate style percentages. Follows shields.io color conventions: brightgreen >= 100, green >= 90, yellow >= 80, orange >= 60. --- ### `percentageToTier` Maps a numeric percentage to a tier (icon, color, label) using configurable thresholds. Tiers are matched by their highest `min` that is `<= value`; a `value` below every tier's `min` (e.g. a negative percentage, or custom tiers that don't cover down to 0) falls back to the tier with the lowest `min` — there's always a match as long as `tiers` is non-empty. ```typescript import { percentageToTier } from '@helpers4/ci'; percentageToTier(value: number, tiers: readonly PercentageTier[]): PercentageTier ``` **Parameters:** - `value: number` — The percentage to classify (typically 0-100, but not clamped) - `tiers: readonly PercentageTier[]` (default: `DEFAULT_PERCENTAGE_TIERS`) — Threshold tiers to match against, in any order (defaults to DEFAULT_PERCENTAGE_TIERS) **Returns:** `PercentageTier` — The matched tier **Examples:** *Coverage report tier* Maps a coverage percentage to a tier using the built-in default thresholds. ```typescript percentageToTier(95.2) // => { min: 90, icon: '🟢', color: 'green', label: 'excellent' } percentageToTier(42) // => { min: 0, icon: '🔴', color: 'red', label: 'poor' } ``` *Custom pass/fail tiers* Supply your own tiers, e.g. a simple two-tier pass/fail gate. ```typescript const tiers = [ { min: 50, icon: '🟢', color: 'green', label: 'pass' }, { min: 0, icon: '🔴', color: 'red', label: 'fail' }, ]; percentageToTier(75, tiers) // => { min: 50, icon: '🟢', color: 'green', label: 'pass' } percentageToTier(25, tiers) // => { min: 0, icon: '🔴', color: 'red', label: 'fail' } ``` --- ### `statusToBadge` Maps a CI/CD job status to an inline code badge string. | Status | Badge | |--------|-------| | `success` | `` `passing` `` | | `failure` | `` `failing` `` | | `skipped` | `` `skipped` `` | | *(other)* | `` `unknown` `` | ```typescript import { statusToBadge } from '@helpers4/ci'; statusToBadge(status: CiStatus): string ``` **Parameters:** - `status: CiStatus` — The CI/CD job status **Returns:** `string` — A Markdown inline-code badge **Examples:** *Map CI status to a Markdown badge* Returns a Markdown code-span badge string for the given CI status. ```typescript statusToBadge('success') // => '`passing`' statusToBadge('failure') // => '`failing`' statusToBadge('skipped') // => '`skipped`' statusToBadge('pending') // => '`unknown`' ``` --- ### `statusToIcon` Maps a CI/CD job status to an emoji icon. | Status | Icon | |--------|------| | `success` | ✅ | | `failure` | ❌ | | `skipped` | ⏭️ | | *(other)* | ⚠️ | ```typescript import { statusToIcon } from '@helpers4/ci'; statusToIcon(status: CiStatus): string ``` **Parameters:** - `status: CiStatus` — The CI/CD job status **Returns:** `string` — An emoji representing the status **Examples:** *Map CI status to icon* Returns an emoji icon matching the given CI status. ```typescript statusToIcon('success') // => '✅' statusToIcon('failure') // => '❌' statusToIcon('skipped') // => '⏭️' statusToIcon('pending') // => '⚠️' ``` --- ## color Package: `@helpers4/color` ### `argbToRgb` Converts a 32-bit packed ARGB integer (as used by e.g. Chromium's `Local State` profile `background_color` field) into a CSS `rgb()` string. The alpha byte (top 8 bits) is read but discarded — the result is always opaque. ```typescript import { argbToRgb } from '@helpers4/color'; argbToRgb(argb: number): string ``` **Parameters:** - `argb: number` — A 32-bit integer where bits 24-31 are alpha, 16-23 are red, 8-15 are green, and 0-7 are blue **Returns:** `string` — A `rgb(r,g,b)` CSS color string **Examples:** *Convert a packed ARGB integer to a CSS color* The top byte (alpha) is discarded — the result is always opaque. ```typescript argbToRgb(0xffff0000) // => 'rgb(255,0,0)' ``` *Alpha byte does not affect the result* Two ARGB values differing only in the alpha byte produce the same rgb() string. ```typescript argbToRgb(0x00ff0000) === argbToRgb(0x80ff0000) // => true ``` --- ### `hexToRgb` Parses a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` — the leading `#` is optional) into its RGB(A) channels. ```typescript import { hexToRgb } from '@helpers4/color'; hexToRgb(hex: string): RgbColor | null ``` **Parameters:** - `hex: string` — The hex color string to parse **Returns:** `RgbColor | null` — The parsed color, or `null` if `hex` is not a valid hex color **Examples:** *Parse a hex color into RGB channels* The leading # is optional; shorthand 3/4-digit forms are also supported. ```typescript hexToRgb('#ff0000') // => { r: 255, g: 0, b: 0, a: 1 } ``` *Returns null for an invalid color* Lets you branch on parse failure instead of getting a garbage color. ```typescript hexToRgb('not-a-color') // => null ``` --- ### `hslToRgb` Converts an HSL(A) color into RGB(A). ```typescript import { hslToRgb } from '@helpers4/color'; hslToRgb(color: HslColor): RgbColor ``` **Parameters:** - `color: HslColor` — The color to convert. `h` is normalized modulo 360 (negative values wrap around), `s`/`l` are expected in 0-100, `a` defaults to 1 (opaque) when omitted. **Returns:** `RgbColor` — The equivalent RGB(A) color, with `r`/`g`/`b` rounded to 0-255 **Examples:** *Convert an HSL color to RGB* Useful after generating a hue programmatically (e.g. evenly spaced chart colors). ```typescript hslToRgb({ h: 120, s: 100, l: 50 }) // => { r: 0, g: 255, b: 0, a: 1 } ``` *Hue wraps around 360 degrees* Negative or out-of-range hues are normalized before conversion. ```typescript hslToRgb({ h: -360, s: 100, l: 50 }) // => { r: 255, g: 0, b: 0, a: 1 } (same as h: 0) ``` --- ### `rgbToHex` Converts an RGB(A) color into a hex color string. `r`/`g`/`b` are clamped to 0-255 and rounded to the nearest integer before formatting. The alpha channel is only appended (as `#rrggbbaa`) when it is below 1 — fully opaque colors format as the plain 6-digit `#rrggbb`. ```typescript import { rgbToHex } from '@helpers4/color'; rgbToHex(color: RgbColor): string ``` **Parameters:** - `color: RgbColor` — The color to convert. `a` defaults to 1 (opaque) when omitted. **Returns:** `string` — A lowercase hex color string **Examples:** *Format an opaque color as hex* Alpha defaults to 1 (opaque) and is omitted from the output. ```typescript rgbToHex({ r: 255, g: 0, b: 0 }) // => '#ff0000' ``` *Include alpha when the color is translucent* A non-opaque color formats as an 8-digit #rrggbbaa string. ```typescript rgbToHex({ r: 0, g: 255, b: 0, a: 0.5 }) // => '#00ff0080' ``` --- ### `rgbToHsl` Converts an RGB(A) color into HSL(A). `h`/`s`/`l` are rounded to 1 decimal place to avoid floating-point noise. ```typescript import { rgbToHsl } from '@helpers4/color'; rgbToHsl(color: RgbColor): HslColor ``` **Parameters:** - `color: RgbColor` — The color to convert. `r`/`g`/`b` are expected in 0-255, `a` defaults to 1 (opaque) when omitted. **Returns:** `HslColor` — The equivalent HSL(A) color: `h` in 0-360, `s`/`l` in 0-100 **Examples:** *Convert an RGB color to HSL* Useful for generating tints/shades by adjusting lightness. ```typescript rgbToHsl({ r: 255, g: 0, b: 0 }) // => { h: 0, s: 100, l: 50, a: 1 } ``` *Grayscale colors have no saturation* When r, g, and b are equal, the color is achromatic (s = 0). ```typescript rgbToHsl({ r: 128, g: 128, b: 128 }) // => { h: 0, s: 0, l: 50.2, a: 1 } ``` --- ## commit Package: `@helpers4/commit` ### `analyzeCommits` Analyses a list of commits to suggest a semantic version bump. Each commit is parsed via `parseConventionalCommit`. The body is also scanned for `BREAKING CHANGE:` / `BREAKING-CHANGE:` markers. The bump rule is: - any breaking change → `'major'` - otherwise any `feat` → `'minor'` - otherwise any `fix` → `'patch'` - otherwise (non-empty list of non-conventional commits) → `'patch'` - empty list → `'patch'` with reason "No commits to analyse" ```typescript import { analyzeCommits } from '@helpers4/commit'; analyzeCommits(commits: readonly AnalyzableCommit[]): CommitAnalysis ``` **Parameters:** - `commits: readonly AnalyzableCommit[]` — Iterable of commits to analyse. Only `subject` is required. **Returns:** `CommitAnalysis` — Aggregated analysis with the suggested bump and reason. **Examples:** *Suggest a semver bump from a list of commits* Walks through commits and suggests `major`, `minor`, or `patch` based on Conventional Commits. ```typescript analyzeCommits([ { subject: 'feat: add login' }, { subject: 'fix: handle null' }, ]) // => { suggestedBump: 'minor', hasFeatures: true, hasFixes: true, ... } ``` *Promote to major on breaking change* A `!` marker or a `BREAKING CHANGE:` footer always promotes the suggestion to `major`. ```typescript analyzeCommits([{ subject: 'feat!: drop v1 API' }]).suggestedBump // => 'major' ``` --- ### `buildConventionalCommitRegex` Builds a regular expression matching the **subject line** of a Conventional Commits message. The returned regex exposes four capture groups: 1. type 2. scope (or `undefined` when absent) 3. breaking marker (`'!'` or `undefined`) 4. description ```typescript import { buildConventionalCommitRegex } from '@helpers4/commit'; buildConventionalCommitRegex(options: ConventionalCommitOptions): RegExp ``` **Parameters:** - `options: ConventionalCommitOptions` (default: `{}`) — Constrain accepted types/scopes and toggle scope requirement. **Returns:** `RegExp` — Regex anchored on `^...$` matching the subject line only. **Examples:** *Match the default Conventional Commits format* Returns a regex matching `type(scope)?!?: description` on the subject line. ```typescript const regex = buildConventionalCommitRegex(); regex.test('feat(api): add endpoint') // => true regex.test('not a commit') // => false ``` *Restrict accepted types and require a scope* Constrain accepted types and force the scope segment to be present. ```typescript const regex = buildConventionalCommitRegex({ types: ['feat', 'fix'], requireScope: true, }); regex.test('feat(api): x') // => true regex.test('feat: missing scope') // => false regex.test('chore(api): wrong type') // => false ``` --- ### `isConventionalCommit` Checks whether a commit message's subject line follows the Conventional Commits format constrained by the given options. Only the first line is inspected — body and footer are ignored. ```typescript import { isConventionalCommit } from '@helpers4/commit'; isConventionalCommit(message: string, options?: ConventionalCommitOptions): boolean ``` **Parameters:** - `message: string` — Full commit message or just its subject line. - `options?: ConventionalCommitOptions` — Optional constraints (allowed types/scopes, scope requirement). **Returns:** `boolean` — `true` when the subject line matches; `false` otherwise. **Examples:** *Validate a commit subject* Returns `true` when the first line follows the Conventional Commits format. ```typescript isConventionalCommit('feat(api): add endpoint') // => true isConventionalCommit('hello world') // => false ``` *Restrict accepted types* Reject any commit whose type is not in the supplied allowlist. ```typescript isConventionalCommit('chore: x', { types: ['feat', 'fix'] }) // => false isConventionalCommit('feat: x', { types: ['feat', 'fix'] }) // => true ``` --- ### `parseConventionalCommit` Parses a Conventional Commits message into a structured object. The first line is matched against the regex produced by `buildConventionalCommitRegex(options)`. The remaining content is split into a `body` and an optional trailing `footer` block (lines matching `Token: value` / `Token #value`, including `BREAKING CHANGE: ...`). ```typescript import { parseConventionalCommit } from '@helpers4/commit'; parseConventionalCommit(message: string, options?: ConventionalCommitOptions): ParsedConventionalCommit | null ``` **Parameters:** - `message: string` — Full commit message (subject + optional body/footer). - `options?: ConventionalCommitOptions` — Optional constraints forwarded to the regex builder. **Returns:** `ParsedConventionalCommit | null` — Parsed commit object, or `null` when the subject is not conventional. **Examples:** *Parse a Conventional Commits subject* Extracts type, scope, breaking flag, and description. ```typescript parseConventionalCommit('feat(api)!: add v2') // => { type: 'feat', scope: 'api', breaking: true, description: 'add v2', body: '', footer: '' } ``` *Detect breaking changes from the footer* A `BREAKING CHANGE:` footer flags the commit as breaking even without the `!` marker. ```typescript parseConventionalCommit('feat: add option\n\nBREAKING CHANGE: drops old config').breaking // => true ``` *Returns null on a non-conventional message* Non-matching subjects return `null` rather than throwing. ```typescript parseConventionalCommit('hello world') // => null ``` --- ## date Package: `@helpers4/date` ### `addDays` Adds days to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if the input is invalid. ```typescript import { addDays } from '@helpers4/date'; addDays(date: DateLike, amount: number): Date | null ``` **Parameters:** - `date: DateLike` — The base date - `amount: number` — Number of days to add (negative to subtract) **Returns:** `Date | null` — A new Date, or `null` if the input is invalid **Examples:** *addDays* ```typescript ```ts addDays('2025-01-19', 10) // => Date(2025-01-29) addDays('2025-01-19', -5) // => Date(2025-01-14) ``` ``` --- ### `addMonths` Adds months to a date. Returns a **new** `Date` — the original is never mutated. When the resulting month has fewer days, JavaScript clamps to the next month (e.g. Jan 31 + 1 month → Mar 3). Use with caution. Returns `null` if the input is invalid. ```typescript import { addMonths } from '@helpers4/date'; addMonths(date: DateLike, amount: number): Date | null ``` **Parameters:** - `date: DateLike` — The base date - `amount: number` — Number of months to add (negative to subtract) **Returns:** `Date | null` — A new Date, or `null` if the input is invalid **Examples:** *addMonths* ```typescript ```ts addMonths('2025-01-15', 1) // => Date(2025-02-15) addMonths('2025-01-31', 1) // => Date(2025-03-03) — overflow addMonths('2025-03-15', -1) // => Date(2025-02-15) ``` ``` --- ### `addYears` Adds years to a date. Returns a **new** `Date` — the original is never mutated. Returns `null` if the input is invalid. ```typescript import { addYears } from '@helpers4/date'; addYears(date: DateLike, amount: number): Date | null ``` **Parameters:** - `date: DateLike` — The base date - `amount: number` — Number of years to add (negative to subtract) **Returns:** `Date | null` — A new Date, or `null` if the input is invalid **Examples:** *addYears* ```typescript ```ts addYears('2025-01-19', 1) // => Date(2026-01-19) addYears('2024-02-29', 1) // => Date(2025-03-01) — leap year overflow addYears('2025-06-15', -2) // => Date(2023-06-15) ``` ``` --- ### `clampDate` Clamps a date to a [min, max] range. Returns a **new** `Date` — the original is never mutated. Returns `null` if any of the inputs is invalid. ```typescript import { clampDate } from '@helpers4/date'; clampDate(date: DateLike, min: DateLike, max: DateLike): Date | null ``` **Parameters:** - `date: DateLike` — The date to clamp - `min: DateLike` — The minimum allowed date - `max: DateLike` — The maximum allowed date **Returns:** `Date | null` — A new Date clamped to the range, or `null` if any input is invalid **Examples:** *clampDate* ```typescript ```ts clampDate('2025-06-15', '2025-01-01', '2025-03-31') // => Date(2025-03-31) — clamped to max clampDate('2025-02-15', '2025-01-01', '2025-03-31') // => Date(2025-02-15) — within range, unchanged ``` ``` --- ### `compare` Comparison of two dates. Accepts any DateLike input (Date, timestamp, or date string). ```typescript import { compare } from '@helpers4/date'; compare(dateA: DateLike, dateB: DateLike, options: DateCompareOptions): boolean ``` **Parameters:** - `dateA: DateLike` — First date to compare - `dateB: DateLike` — Second date to compare - `options: DateCompareOptions` (default: `{}`) — Comparison options **Returns:** `boolean` — `true` if dates are identical according to the specified precision, `false` otherwise **Examples:** *Compare dates with millisecond precision* By default, two identical Date objects are equal. ```typescript const d = new Date('2025-01-19T12:00:00Z'); compare(d, new Date('2025-01-19T12:00:00Z')) // => true ``` *Compare only by day* Using day precision ignores the time part. ```typescript compare( new Date('2025-01-19T08:00:00Z'), new Date('2025-01-19T23:59:59Z'), { precision: 'days' } ) // => true ``` --- ### `daysInMonth` Returns the number of days in the given month of the given year. Month is **1-based** (1 = January, 12 = December) to match human convention and ISO 8601 (unlike `Date.getMonth()` which is 0-based). Returns `NaN` if the month is out of range. ```typescript import { daysInMonth } from '@helpers4/date'; daysInMonth(year: number, month: number): number ``` **Parameters:** - `year: number` — A full year number (e.g. 2025) - `month: number` — 1-based month number (1–12) **Returns:** `number` — Number of days in that month, or `NaN` for invalid month **Examples:** *daysInMonth* ```typescript ```ts daysInMonth(2025, 1) // => 31 (January) daysInMonth(2025, 2) // => 28 (February, non-leap) daysInMonth(2024, 2) // => 29 (February, leap) daysInMonth(2025, 4) // => 30 (April) ``` ``` --- ### `difference` Calculates the difference between two dates in the specified unit. Accepts any DateLike input (Date, timestamp, or date string). Unlike the removed `daysDifference`, the default `'days'` unit is **not** rounded — it returns the exact fractional number of days. Wrap the result in `Math.round` if you need a whole-number day count. ```typescript import { difference } from '@helpers4/date'; difference(dateA: DateLike, dateB: DateLike, options: DateDifferenceOptions): number ``` **Parameters:** - `dateA: DateLike` — First date - `dateB: DateLike` — Second date - `options: DateDifferenceOptions` (default: `{}`) — Difference options **Returns:** `number` — The difference between the two dates, or `NaN` if either date is invalid **Examples:** *difference* ```typescript ```ts difference('2025-01-01', '2025-01-10') // => 9 difference('2025-01-01T00:00:00Z', '2025-01-01T12:00:00Z') // => 0.5 (fractional — use Math.round(difference(a, b)) for a whole-day count) difference('2025-01-01T00:00:00Z', '2025-01-01T02:30:00Z', { unit: 'hours' }) // => 2.5 difference('2025-01-10', '2025-01-01', { absolute: false }) // => -9 ``` ``` --- ### `eachDay` Returns an array of `Date` objects for each day from `start` to `end` (inclusive). Both boundaries are included. If `start > end`, an empty array is returned. Returns an empty array if either input is invalid. ```typescript import { eachDay } from '@helpers4/date'; eachDay(start: DateLike, end: DateLike): Date[] ``` **Parameters:** - `start: DateLike` — Start date (inclusive) - `end: DateLike` — End date (inclusive) **Returns:** `Date[]` — An array of Date objects, one per day **Examples:** *eachDay* ```typescript ```ts eachDay('2025-01-01', '2025-01-03') // => [Date(2025-01-01), Date(2025-01-02), Date(2025-01-03)] ``` ``` --- ### `eachMonth` Returns an array of `Date` objects for the first day of each month from `start` to `end` (inclusive). Each returned Date is normalized to the 1st of the month at 00:00:00.000. If `start > end`, an empty array is returned. Returns an empty array if either input is invalid. ```typescript import { eachMonth } from '@helpers4/date'; eachMonth(start: DateLike, end: DateLike): Date[] ``` **Parameters:** - `start: DateLike` — Start date (inclusive — the month containing this date is included) - `end: DateLike` — End date (inclusive — the month containing this date is included) **Returns:** `Date[]` — An array of Date objects, one per month (each on the 1st) **Examples:** *eachMonth* ```typescript ```ts eachMonth('2025-01-15', '2025-04-10') // => [Date(2025-01-01), Date(2025-02-01), Date(2025-03-01), Date(2025-04-01)] ``` ``` --- ### `endOf` Returns a new `Date` set to the **end** of the given unit. - `'day'` — 23:59:59.999 - `'month'` — last day of the month, 23:59:59.999 - `'year'` — December 31st, 23:59:59.999 Returns `null` if the input is invalid. ```typescript import { endOf } from '@helpers4/date'; endOf(date: DateLike, unit: DateTruncUnit): Date | null ``` **Parameters:** - `date: DateLike` — The base date - `unit: DateTruncUnit` — The unit to round to **Returns:** `Date | null` — A new Date at the end of the unit, or `null` **Examples:** *endOf* ```typescript ```ts endOf('2025-06-15T14:30:00Z', 'day') // => 2025-06-15T23:59:59.999Z endOf('2025-06-15T14:30:00Z', 'month') // => 2025-06-30T23:59:59.999Z endOf('2025-06-15T14:30:00Z', 'year') // => 2025-12-31T23:59:59.999Z ``` ``` --- ### `ensureDate` Safely converts a date-like value to a valid `Date` object, or returns `null`. Accepts `Date`, timestamps (seconds or milliseconds, auto-detected), date strings, and objects with an `epochMilliseconds` property (e.g. `Temporal.Instant`, `Temporal.ZonedDateTime`). Returns `null` for `null`, `undefined`, empty strings, `0`, and any value that produces an invalid `Date`. This is the date equivalent of ensureArray — it normalizes flexible input into a guaranteed type (or a safe fallback). ```typescript import { ensureDate } from '@helpers4/date'; ensureDate(input: DateLike | null | undefined): Date | null ``` **Parameters:** - `input: DateLike | null | undefined` — A date-like value to convert **Returns:** `Date | null` — A valid `Date` object, or `null` if the input is invalid **Examples:** *ensureDate* ```typescript ```ts ensureDate('2025-01-19T12:00:00Z') // => Date ensureDate(1737290400) // => Date (from Unix seconds) ensureDate(1737290400000) // => Date (from milliseconds) ensureDate(new Date()) // => Date (same reference) ensureDate(null) // => null ensureDate('invalid') // => null ``` ``` --- ### `formatDuration` Formats a duration in milliseconds as a compact human-readable string. Produces output like `"1h 23m 45s"`. Zero-valued leading units are omitted (e.g. `"23m 45s"` instead of `"0h 23m 45s"`), but trailing zeros are kept up to the minimum unit (`"1h 0m 0s"` when `minUnit` is `'seconds'`). Negative durations are prefixed with `"-"`. A zero duration returns `"0s"` (or `"0m"` / `"0h"` depending on `minUnit`). ```typescript import { formatDuration } from '@helpers4/date'; formatDuration(ms: number, options: FormatDurationOptions): string ``` **Parameters:** - `ms: number` — Duration in milliseconds - `options: FormatDurationOptions` (default: `{}`) — Optional configuration **Returns:** `string` — A compact duration string **Examples:** *formatDuration* ```typescript ```ts formatDuration(5025000) // => "1h 23m 45s" formatDuration(45000) // => "45s" formatDuration(3600000) // => "1h 0m 0s" formatDuration(5025000, { minUnit: 'minutes' }) // => "1h 23m" formatDuration(5025000, { padded: true }) // => "01h 23m 45s" formatDuration(-5025000) // => "-1h 23m 45s" ``` ``` --- ### `formatInTimezone` Formats a date in a specific IANA timezone using `Intl.DateTimeFormat`. Returns `null` if the date or timezone is invalid. ```typescript import { formatInTimezone } from '@helpers4/date'; formatInTimezone(date: DateLike, tz: string, options: FormatInTimezoneOptions): string | null ``` **Parameters:** - `date: DateLike` — The date to format - `tz: string` — IANA timezone identifier (e.g. `'Asia/Tokyo'`) - `options: FormatInTimezoneOptions` (default: `{}`) — Optional locale and format configuration **Returns:** `string | null` — A formatted date string, or `null` **Examples:** *formatInTimezone* ```typescript ```ts formatInTimezone('2025-01-19T12:00:00Z', 'Asia/Tokyo') // => "1/19/2025, 9:00:00 PM" (en-US default) formatInTimezone('2025-01-19T12:00:00Z', 'Europe/Paris', { locale: 'fr-FR', formatOptions: { dateStyle: 'long', timeStyle: 'short' }, }) // => "19 janvier 2025, 13:00" ``` ``` --- ### `fromMillis` Creates a `Date` from a timestamp in **milliseconds**. Use this when receiving a timestamp from a JS-native source (e.g. `Date.now()`, `performance.timeOrigin`). No heuristic — the input is always treated as milliseconds. ```typescript import { fromMillis } from '@helpers4/date'; fromMillis(ms: number): Date | null ``` **Parameters:** - `ms: number` — Milliseconds since the Unix epoch **Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input **Examples:** *fromMillis* ```typescript ```ts fromMillis(1737288000000) // => Date('2025-01-19T12:00:00Z') fromMillis(0) // => Date('1970-01-01T00:00:00Z') fromMillis(NaN) // => null ``` ``` --- ### `fromSeconds` Creates a `Date` from a timestamp in **seconds**. Use this when receiving a timestamp from a backend that sends seconds (e.g. Java `Instant.getEpochSecond()`). No heuristic — the input is always treated as seconds. ```typescript import { fromSeconds } from '@helpers4/date'; fromSeconds(seconds: number): Date | null ``` **Parameters:** - `seconds: number` — Seconds since the Unix epoch **Returns:** `Date | null` — A valid `Date`, or `null` for `NaN` / non-finite input **Examples:** *fromSeconds* ```typescript ```ts fromSeconds(1737288000) // => Date('2025-01-19T12:00:00Z') fromSeconds(0) // => Date('1970-01-01T00:00:00Z') fromSeconds(NaN) // => null ``` ``` --- ### `getTimezoneOffset` Returns the UTC offset **in minutes** for the given IANA timezone at a specific point in time. A positive value means the timezone is **ahead** of UTC (e.g. `+60` for CET). Returns `null` if the timezone is invalid or the date cannot be parsed. The implementation uses `Intl.DateTimeFormat` to extract the local representation in the target timezone, then computes the delta from UTC. ```typescript import { getTimezoneOffset } from '@helpers4/date'; getTimezoneOffset(tz: string, date: DateLike): number | null ``` **Parameters:** - `tz: string` — IANA timezone identifier (e.g. `'America/New_York'`) - `date: DateLike` (default: `...`) — Reference date (defaults to now) **Returns:** `number | null` — Offset in minutes, or `null` if inputs are invalid **Examples:** *getTimezoneOffset* ```typescript ```ts getTimezoneOffset('America/New_York', '2025-01-19T12:00:00Z') // => -300 (EST) getTimezoneOffset('Europe/Paris', '2025-07-19T12:00:00Z') // => 120 (CEST) ``` ``` --- ### `isBusinessDay` Checks whether a date falls on a business day (i.e. **not** a weekend day). This is the logical inverse of isWeekend. By default, business days are Monday through Friday. Pass a custom `weekendDays` to adapt to other calendars. > **Note:** This helper does **not** account for public holidays — those are > country- and region-specific. Use it in combination with your own holiday > list if needed. Returns `false` if the input is invalid. ```typescript import { isBusinessDay } from '@helpers4/date'; isBusinessDay(date: DateLike, weekendDays: readonly WeekDay[]): boolean ``` **Parameters:** - `date: DateLike` — The date to check - `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`) **Returns:** `boolean` — `true` if the date is not a weekend day **Examples:** *isBusinessDay* ```typescript ```ts isBusinessDay('2025-01-20') // => true (Monday) isBusinessDay('2025-01-18') // => false (Saturday) // UAE weekend (Friday + Saturday) const uaeWeekend = [WeekDays.Friday, WeekDays.Saturday] as const; isBusinessDay('2025-01-19', uaeWeekend) // => true (Sunday = workday) isBusinessDay('2025-01-17', uaeWeekend) // => false (Friday = weekend) ``` ``` --- ### `isLeapYear` Returns `true` if the given year is a leap year. A year is a leap year when it is divisible by 4, **except** century years which must also be divisible by 400. ```typescript import { isLeapYear } from '@helpers4/date'; isLeapYear(year: number): boolean ``` **Parameters:** - `year: number` — A full year number (e.g. 2024) **Returns:** `boolean` — `true` if the year is a leap year **Examples:** *isLeapYear* ```typescript ```ts isLeapYear(2024) // => true isLeapYear(2025) // => false isLeapYear(2000) // => true (divisible by 400) isLeapYear(1900) // => false (century, not divisible by 400) ``` ``` --- ### `isSameDay` Checks if two dates are the same day. Accepts any DateLike input (Date, timestamp, or date string). ```typescript import { isSameDay } from '@helpers4/date'; isSameDay(date1: DateLike, date2: DateLike): boolean ``` **Parameters:** - `date1: DateLike` — First date - `date2: DateLike` — Second date **Returns:** `boolean` — True if same day, false otherwise (including when either date is invalid) **Examples:** *Same day, different times* Returns true when both dates are on the same calendar day. ```typescript isSameDay(new Date('2025-01-19T08:00:00'), new Date('2025-01-19T22:00:00')) // => true ``` --- ### `isSameMonth` Checks if two dates are in the same month (and year). Accepts any DateLike input (Date, timestamp, or date string). ```typescript import { isSameMonth } from '@helpers4/date'; isSameMonth(date1: DateLike, date2: DateLike): boolean ``` **Parameters:** - `date1: DateLike` — First date - `date2: DateLike` — Second date **Returns:** `boolean` — True if same month and year, false otherwise (including when either date is invalid) **Examples:** *isSameMonth* ```typescript ```ts isSameMonth('2025-01-01', '2025-01-31') // => true isSameMonth('2025-01-31', '2025-02-01') // => false ``` ``` --- ### `isSameYear` Checks if two dates are in the same year. Accepts any DateLike input (Date, timestamp, or date string). ```typescript import { isSameYear } from '@helpers4/date'; isSameYear(date1: DateLike, date2: DateLike): boolean ``` **Parameters:** - `date1: DateLike` — First date - `date2: DateLike` — Second date **Returns:** `boolean` — True if same year, false otherwise (including when either date is invalid) **Examples:** *isSameYear* ```typescript ```ts isSameYear('2025-01-01', '2025-12-31') // => true isSameYear('2024-12-31', '2025-01-01') // => false ``` ``` --- ### `isTimestampInSeconds` Checks if a timestamp is likely in seconds (Java/Unix style) vs milliseconds (JavaScript style) ```typescript import { isTimestampInSeconds } from '@helpers4/date'; isTimestampInSeconds(timestamp: number): boolean ``` **Parameters:** - `timestamp: number` — The timestamp to check **Returns:** `boolean` — True if timestamp appears to be in seconds **Examples:** *Detect a Unix timestamp in seconds* Returns true for timestamps that are likely in seconds (Java/Unix style). ```typescript isTimestampInSeconds(1737290400) // => true ``` *Normalize a Unix timestamp to milliseconds* Converts a timestamp in seconds to JavaScript milliseconds. ```typescript normalizeTimestamp(1737290400) // => 1737290400000 ``` --- ### `isValid` Checks if a value is a valid Date instance (not `Invalid Date`). Unlike `isDate` (in `type/`), this also verifies that the internal timestamp is not `NaN`. ```typescript import { isValid } from '@helpers4/date'; isValid(value: unknown): value is Date ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Date` — True if value is a Date instance with a valid time value **Examples:** *isValid* ```typescript ```ts isValid(new Date()) // => true isValid(new Date('invalid')) // => false isValid('2023-01-01') // => false (not a Date instance) ``` ``` --- ### `isValidDateString` Checks whether a string can be parsed into a valid `Date`. Uses the native `Date` constructor. Returns `false` for empty strings and any string that produces an Invalid Date. > **Caveat:** The native parser is lenient and implementation-dependent > for non-ISO formats. For strict format validation, prefer a dedicated > library or manual regex checks. ```typescript import { isValidDateString } from '@helpers4/date'; isValidDateString(input: string): boolean ``` **Parameters:** - `input: string` — The string to validate **Returns:** `boolean` — `true` if `new Date(input)` produces a valid date **Examples:** *isValidDateString* ```typescript ```ts isValidDateString('2025-01-19') // => true isValidDateString('2025-01-19T12:00:00Z') // => true isValidDateString('Jan 19, 2025') // => true isValidDateString('not a date') // => false isValidDateString('') // => false ``` ``` --- ### `isWeekend` Checks whether a date falls on a weekend day. By default, weekend days are **Saturday** and **Sunday** (Western convention). Pass a custom `weekendDays` tuple to adapt to other calendars (e.g. `[5, 6]` for Friday/Saturday in many Middle-Eastern countries). Returns `false` if the input is invalid. ```typescript import { isWeekend } from '@helpers4/date'; isWeekend(date: DateLike, weekendDays: readonly WeekDay[]): boolean ``` **Parameters:** - `date: DateLike` — The date to check - `weekendDays: readonly WeekDay[]` (default: `DEFAULT_WEEKEND`) — Override which days count as weekend (default: `[0, 6]`) **Returns:** `boolean` — `true` if the date's day-of-week is in `weekendDays` **Examples:** *isWeekend* ```typescript ```ts isWeekend('2025-01-18') // => true (Saturday) isWeekend('2025-01-19') // => true (Sunday) isWeekend('2025-01-20') // => false (Monday) // Middle-Eastern weekend (Friday + Saturday) isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday]) // => true isWeekend('2025-01-19', [WeekDays.Friday, WeekDays.Saturday]) // => false ``` ``` --- ### `isWithinRange` Checks whether a date falls within a range (inclusive on both ends). Returns `false` if any of the inputs is invalid. ```typescript import { isWithinRange } from '@helpers4/date'; isWithinRange(date: DateLike, start: DateLike, end: DateLike): boolean ``` **Parameters:** - `date: DateLike` — The date to check - `start: DateLike` — Start of the range (inclusive) - `end: DateLike` — End of the range (inclusive) **Returns:** `boolean` — `true` if `start <= date <= end` **Examples:** *isWithinRange* ```typescript ```ts isWithinRange('2025-06-15', '2025-01-01', '2025-12-31') // => true isWithinRange('2024-06-15', '2025-01-01', '2025-12-31') // => false ``` ``` --- ### `listTimezones` Returns the list of IANA timezone identifiers supported by the runtime. Wraps `Intl.supportedValuesOf('timeZone')` which is available in Node 18+, Chrome 93+, Firefox 93+, Safari 15.4+. ```typescript import { listTimezones } from '@helpers4/date'; listTimezones(): string[] ``` **Returns:** `string[]` — An array of IANA timezone strings (e.g. `['Africa/Abidjan', …, 'US/Pacific']`) **Examples:** *listTimezones* ```typescript ```ts listTimezones() // => ['Africa/Abidjan', 'Africa/Accra', …] ``` ``` --- ### `normalizeTimestamp` Converts a timestamp to JavaScript milliseconds format ```typescript import { normalizeTimestamp } from '@helpers4/date'; normalizeTimestamp(timestamp: number): number ``` **Parameters:** - `timestamp: number` — The timestamp (in seconds or milliseconds) **Returns:** `number` — Timestamp in milliseconds --- ### `overlaps` Checks whether two date ranges overlap. Two ranges overlap when `rangeA.start <= rangeB.end` AND `rangeB.start <= rangeA.end` (inclusive on both ends). Returns `false` if any date is invalid. ```typescript import { overlaps } from '@helpers4/date'; overlaps(rangeA: DateRange, rangeB: DateRange): boolean ``` **Parameters:** - `rangeA: DateRange` — First date range - `rangeB: DateRange` — Second date range **Returns:** `boolean` — `true` if the ranges share at least one point in time **Examples:** *overlaps* ```typescript ```ts overlaps( { start: '2025-01-01', end: '2025-06-30' }, { start: '2025-03-01', end: '2025-12-31' } ) // => true overlaps( { start: '2025-01-01', end: '2025-02-28' }, { start: '2025-03-01', end: '2025-12-31' } ) // => false ``` ``` --- ### `parseDuration` Parses a compact duration string (as produced by formatDuration, e.g. `"1h 23m 45s"`) back into milliseconds. Accepts any combination/order of `h`/`m`/`s` segments, with or without spaces between them (`"1h30m"` and `"1h 30m"` both work). A single leading `-` negates the whole duration, matching `formatDuration`'s output. Returns `null` when no valid segment is found. ```typescript import { parseDuration } from '@helpers4/date'; parseDuration(str: string): number | null ``` **Parameters:** - `str: string` — The duration string to parse **Returns:** `number | null` — The duration in milliseconds, or `null` if unparseable **Examples:** *Parse a compact duration string into milliseconds* The inverse of formatDuration() — accepts the same "1h 23m 45s" style output. ```typescript parseDuration('1h 23m 45s') // => 5025000 ``` *Returns null for an unparseable string* Lets you branch on parse failure instead of silently getting 0 or NaN. ```typescript parseDuration('not a duration') // => null ``` --- ### `startOf` Returns a new `Date` set to the **start** of the given unit. - `'day'` — 00:00:00.000 - `'month'` — 1st of the month, 00:00:00.000 - `'year'` — January 1st, 00:00:00.000 Returns `null` if the input is invalid. ```typescript import { startOf } from '@helpers4/date'; startOf(date: DateLike, unit: DateTruncUnit): Date | null ``` **Parameters:** - `date: DateLike` — The base date - `unit: DateTruncUnit` — The unit to truncate to **Returns:** `Date | null` — A new Date at the start of the unit, or `null` **Examples:** *startOf* ```typescript ```ts startOf('2025-06-15T14:30:00Z', 'day') // => 2025-06-15T00:00:00.000Z startOf('2025-06-15T14:30:00Z', 'month') // => 2025-06-01T00:00:00.000Z startOf('2025-06-15T14:30:00Z', 'year') // => 2025-01-01T00:00:00.000Z ``` ``` --- ### `timeAgo` Formats a date as a human-readable relative time string. Uses `Intl.RelativeTimeFormat` under the hood, making the output locale-aware (e.g. "il y a 3 jours" in French). Returns `null` if the input date is invalid. ```typescript import { timeAgo } from '@helpers4/date'; timeAgo(date: DateLike, options: TimeAgoOptions): string | null ``` **Parameters:** - `date: DateLike` — The date to describe relative to `now` - `options: TimeAgoOptions` (default: `{}`) — Optional configuration (reference date, locale, numeric style) **Returns:** `string | null` — A locale-aware relative time string, or `null` **Examples:** *timeAgo* ```typescript ```ts timeAgo('2025-01-17T00:00:00Z', { now: '2025-01-19T00:00:00Z' }) // => "2 days ago" timeAgo('2025-01-20T00:00:00Z', { now: '2025-01-19T00:00:00Z' }) // => "in 1 day" (or "tomorrow" with numeric: 'auto') timeAgo('2025-01-19T00:00:00Z', { now: '2025-01-19T00:00:05Z' }) // => "5 seconds ago" ``` ``` --- ### `toISO8601` Converts a date to ISO 8601 format Format: YYYY-MM-DDTHH:mm:ss.sssZ ```typescript import { toISO8601 } from '@helpers4/date'; toISO8601(date: DateLike): string | null ``` **Parameters:** - `date: DateLike` — Date to convert (Date object, timestamp, or date string) **Returns:** `string | null` — ISO 8601 formatted string or null if invalid date **Examples:** *Convert to ISO 8601* Formats a date as an ISO 8601 string. ```typescript toISO8601(new Date('2025-01-19T12:30:00Z')) // => '2025-01-19T12:30:00.000Z' ``` *Convert to RFC 3339 (no ms)* RFC 3339 format strips milliseconds by default. ```typescript toRFC3339(new Date('2025-01-19T12:30:45.123Z')) // => '2025-01-19T12:30:45Z' ``` *Convert to RFC 2822* RFC 2822 is used in email and HTTP headers. ```typescript toRFC2822(new Date('2025-01-19T12:30:00Z')) // => 'Sun, 19 Jan 2025 12:30:00 +0000' ``` --- ### `toMillis` Converts a date to a timestamp in **milliseconds** (epoch millis). Use this when you need a plain number from a `DateLike` value (e.g. for `Date.now()` comparisons, localStorage, or JS-native APIs). ```typescript import { toMillis } from '@helpers4/date'; toMillis(date: DateLike): number | null ``` **Parameters:** - `date: DateLike` — The date to convert **Returns:** `number | null` — Milliseconds since the Unix epoch, or `null` for invalid input **Examples:** *toMillis* ```typescript ```ts toMillis('2025-01-19T12:00:00Z') // => 1737288000000 toMillis(null) // => null ``` ``` --- ### `toRFC2822` Converts a date to RFC 2822 format Format: Day, DD Mon YYYY HH:mm:ss +0000 Used in email headers (Date field) and HTTP headers ```typescript import { toRFC2822 } from '@helpers4/date'; toRFC2822(date: DateLike): string | null ``` **Parameters:** - `date: DateLike` — Date to convert (Date object, timestamp, or date string) **Returns:** `string | null` — RFC 2822 formatted string or null if invalid date **Examples:** *toRFC2822* ```typescript ```ts toRFC2822(new Date('2025-01-19T12:30:00Z')) // 'Sun, 19 Jan 2025 12:30:00 +0000' ``` ``` --- ### `toRFC3339` Converts a date to RFC 3339 format Format: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:mm RFC 3339 is a profile of ISO 8601, but without milliseconds by default ```typescript import { toRFC3339 } from '@helpers4/date'; toRFC3339(date: DateLike, includeMilliseconds: boolean): string | null ``` **Parameters:** - `date: DateLike` — Date to convert (Date object, timestamp, or date string) - `includeMilliseconds: boolean` (default: `false`) — Whether to include milliseconds (default: false) **Returns:** `string | null` — RFC 3339 formatted string or null if invalid date **Examples:** *toRFC3339* ```typescript ```ts toRFC3339(new Date('2025-01-19T12:30:45.123Z')) // '2025-01-19T12:30:45Z' toRFC3339(new Date('2025-01-19T12:30:45.123Z'), true) // '2025-01-19T12:30:45.123Z' ``` ``` --- ### `toSeconds` Converts a date to a timestamp in **seconds** (epoch seconds). Use this when sending a date to a backend API that expects seconds (e.g. Java `Instant.getEpochSecond()`, Python `time.time()`). ```typescript import { toSeconds } from '@helpers4/date'; toSeconds(date: DateLike): number | null ``` **Parameters:** - `date: DateLike` — The date to convert **Returns:** `number | null` — Seconds since the Unix epoch, or `null` for invalid input **Examples:** *toSeconds* ```typescript ```ts toSeconds('2025-01-19T12:00:00Z') // => 1737288000 toSeconds(null) // => null ``` ``` --- ### `WeekDays` Named day-of-week constants following the JavaScript `Date.getDay()` convention. Use these instead of raw numbers for readability. **Examples:** *WeekDays* ```typescript ```ts import { WeekDays } from '@helpers4/date'; isWeekend('2025-01-17', [WeekDays.Friday, WeekDays.Saturday]) ``` ``` --- ## function Package: `@helpers4/function` ### `compose` Composes functions right-to-left: `compose(f, g)(x)` is equivalent to `f(g(x))`. The inverse of pipe, which applies functions left-to-right. ```typescript import { compose } from '@helpers4/function'; compose(fn1: function): function ``` **Parameters:** - `fn1: function` **Returns:** `function` — A function that applies `fns` in reverse order ```typescript import { compose } from '@helpers4/function'; compose(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(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(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(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(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(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(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(fn: function): function ``` **Parameters:** - `fn: function` — The function to curry **Returns:** `function` — A curried version of `fn` ```typescript import { curry } from '@helpers4/function'; curry(fn: function): function ``` **Parameters:** - `fn: function` — The function to curry **Returns:** `function` — A curried version of `fn` ```typescript import { curry } from '@helpers4/function'; curry(fn: function): function ``` **Parameters:** - `fn: function` — The function to curry **Returns:** `function` — A curried version of `fn` ```typescript import { curry } from '@helpers4/function'; curry(fn: function): function ``` **Parameters:** - `fn: function` — The function to curry **Returns:** `function` — A curried version of `fn` ```typescript import { curry } from '@helpers4/function'; curry(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(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(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(value: T): T ``` **Parameters:** - `value: T` — The value to return **Returns:** `T` — The same value **Examples:** *Return a primitive unchanged* The value is returned as-is with its type preserved. ```typescript identity(42); // 42 identity('hello'); // 'hello' identity(true); // true ``` *Use as a default mapper* Pass identity where a transform function is required but no transformation is needed. ```typescript [1, 2, 3].map(identity); // [1, 2, 3] ``` --- ### `memoize` Returns a memoized version of the function that caches results. Cache keys are derived via `JSON.stringify`; `undefined` arguments are correctly distinguished from `null`. Arguments that are not JSON-serializable (functions, symbols, class instances, circular references) produce a `null`-equivalent key and are not supported. ```typescript import { memoize } from '@helpers4/function'; memoize(func: function, options?: MemoizeOptions): function ``` **Parameters:** - `func: function` — The function to memoize - `options?: MemoizeOptions` — Optional settings (e.g. `maxSize` to cap memory usage) **Returns:** `function` — The memoized function **Examples:** *Cache function results* The underlying function is only called once for the same arguments. ```typescript let calls = 0; const expensive = memoize((n: number) => { calls++; return n * 2; }); expensive(5); // => 10 (computed) expensive(5); // => 10 (cached) ``` --- ### `negate` Creates a function that negates the result of `predicate`. ```typescript import { negate } from '@helpers4/function'; negate(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(fn: function): OnceFn ``` **Parameters:** - `fn: function` — The function to wrap **Returns:** `OnceFn` — 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(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(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(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(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(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(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(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(fn1: function): function ``` **Parameters:** - `fn1: function` **Returns:** `function` — A function that applies `fns` in order ```typescript import { pipe } from '@helpers4/function'; pipe(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(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(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(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(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(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(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(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(func: function, wait: number): function ``` **Parameters:** - `func: function` — The function to throttle - `wait: number` — The number of milliseconds to throttle invocations to **Returns:** `function` — The throttled function **Examples:** *Throttle rapid calls* The throttled function is invoked at most once per wait period. ```typescript const fn = throttle(() => console.log('tick'), 100); fn(); // executes immediately fn(); // ignored (within wait period) ``` --- ### `unary` Creates a function that calls `fn` with only its first argument, discarding any others. Prevents the classic footgun where a callback expecting extra positional arguments is passed directly to `Array.prototype.map`: `['1', '2', '3'].map(parseInt)` silently passes the array index as `parseInt`'s radix argument, producing `[1, NaN, NaN]`. ```typescript import { unary } from '@helpers4/function'; unary(fn: F): function ``` **Parameters:** - `fn: F` — The function to restrict to one argument **Returns:** `function` — A new function that calls `fn` with only its first argument **Examples:** *Fix the classic .map(parseInt) bug* Array.prototype.map passes (value, index, array) — parseInt reads index as its radix argument. ```typescript ['1', '2', '3'].map(parseInt) // => [1, NaN, NaN] (bug: index used as radix) ['1', '2', '3'].map(unary(parseInt)) // => [1, 2, 3] ``` *Restrict any multi-argument function to one argument* Useful whenever a callback slot passes more arguments than the function should see. ```typescript const double = unary((n: number, factor?: number) => n * (factor ?? 2)); double(21) // => 42 ``` --- ## guard Package: `@helpers4/guard` ### `isArray` Checks if a value is an array. ```typescript import { isArray } from '@helpers4/guard'; isArray(value: unknown): value is unknown[] ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is unknown[]` — True if value is an array **Examples:** *isArray* ```typescript ```ts isArray([1, 2, 3]) // => true isArray('hello') // => false isArray({}) // => false ``` ``` --- ### `isArrayBuffer` Checks if a value is an ArrayBuffer instance. Useful for filtering or type-narrowing in a functional pipeline: `values.filter(isArrayBuffer)` ```typescript import { isArrayBuffer } from '@helpers4/guard'; isArrayBuffer(value: unknown): value is ArrayBuffer ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is ArrayBuffer` — True if value is an ArrayBuffer **Examples:** *Detect an ArrayBuffer* Returns true only for ArrayBuffer instances, not TypedArray views. ```typescript isArrayBuffer(new ArrayBuffer(8)) // => true isArrayBuffer(new Uint8Array(8)) // => false isArrayBuffer('hello') // => false ``` *Filter ArrayBuffers from a mixed array* Use as a predicate in .filter() to extract ArrayBuffer values. ```typescript const values = [new ArrayBuffer(4), 'text', new ArrayBuffer(8), 42]; values.filter(isArrayBuffer) // => [ArrayBuffer(4), ArrayBuffer(8)] ``` --- ### `isArrayLike` Checks if a value is array-like: has a non-negative integer `length` property. Returns `true` for arrays, strings, `arguments` objects, `NodeList`, typed arrays, and any object with a valid `length`. Functions are excluded even though they have a `length` (arity), as they are not considered array-like in practice. ```typescript import { isArrayLike } from '@helpers4/guard'; isArrayLike(value: unknown): value is ArrayLike ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is ArrayLike` — `true` if value is array-like **Examples:** *Detect array-like values* Arrays, strings, and objects with a non-negative integer length are array-like. ```typescript isArrayLike([1, 2, 3]) // => true isArrayLike('hello') // => true isArrayLike({ length: 3 }) // => true isArrayLike({ length: -1 }) // => false isArrayLike(() => {}) // => false (functions excluded) isArrayLike(null) // => false ``` *Convert an array-like value to an array* Use as a guard before Array.from(). ```typescript function toArray(value: unknown): unknown[] { if (isArrayLike(value)) return Array.from(value); return [value]; } toArray([1, 2]) // => [1, 2] toArray('abc') // => ['a', 'b', 'c'] toArray(42) // => [42] ``` --- ### `isAsyncFunction` Checks if a value is an async function. Returns `true` for any function declared with `async`. ```typescript import { isAsyncFunction } from '@helpers4/guard'; isAsyncFunction(value: unknown): value is function ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is function` — True if value is an async function **Examples:** *isAsyncFunction* ```typescript ```ts isAsyncFunction(async () => {}) // => true isAsyncFunction(async function() {}) // => true isAsyncFunction(() => {}) // => false isAsyncFunction(42) // => false ``` ``` --- ### `isAsyncGenerator` Checks if a value is an async generator object (the result of calling an `async function*`). Distinct from isAsyncGeneratorFunction: this predicate targets the *instance* produced by calling an async generator function, not the function itself. ```typescript import { isAsyncGenerator } from '@helpers4/guard'; isAsyncGenerator(value: unknown): value is AsyncGenerator ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is AsyncGenerator` — `true` if value is an AsyncGenerator instance **Examples:** *Detect an async generator instance* Returns true only for the object produced by calling an async function*. ```typescript async function* gen() { yield 1; } isAsyncGenerator(gen()) // => true (instance) isAsyncGenerator(gen) // => false (function) isAsyncGenerator([]) // => false ``` *Distinguish async from sync generators* isAsyncGenerator is false for sync generator instances. ```typescript function* sync() { yield 1; } async function* async_() { yield 1; } isAsyncGenerator(sync()) // => false isAsyncGenerator(async_()) // => true ``` --- ### `isAsyncGeneratorFunction` Checks if a value is an async generator function (an `async function*` declaration or expression). Distinct from isAsyncGenerator: this predicate targets the *function* itself, not the async iterator it produces when called. ```typescript import { isAsyncGeneratorFunction } from '@helpers4/guard'; isAsyncGeneratorFunction(value: unknown): value is AsyncGeneratorFunction ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is AsyncGeneratorFunction` — `true` if value is an AsyncGeneratorFunction **Examples:** *Detect an async generator function* Returns true for async function* declarations and expressions. ```typescript async function* gen() { yield 1; } isAsyncGeneratorFunction(gen) // => true isAsyncGeneratorFunction(gen()) // => false (instance) isAsyncGeneratorFunction(async () => {}) // => false ``` *Distinguish async generator functions from sync generator functions* isAsyncGeneratorFunction is false for sync function*. ```typescript function* sync() { yield 1; } async function* async_() { yield 1; } isAsyncGeneratorFunction(sync) // => false isAsyncGeneratorFunction(async_) // => true ``` --- ### `isAsyncIterable` Checks if a value implements the async iterable protocol. Returns `true` for any object that has a `[Symbol.asyncIterator]()` method, including async generators. Note that regular iterables (arrays, strings, etc.) are **not** async iterables. ```typescript import { isAsyncIterable } from '@helpers4/guard'; isAsyncIterable(value: unknown): value is AsyncIterable ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is AsyncIterable` — `true` if value is async iterable **Examples:** *Detect an async generator* Async generators implement the async iterable protocol. ```typescript async function* stream() { yield 1; yield 2; } isAsyncIterable(stream()) // => true isAsyncIterable([1, 2, 3]) // => false (Iterable, not AsyncIterable) isAsyncIterable('hello') // => false ``` *Guard before for-await-of* Use to type-narrow before consuming a value with for-await-of. ```typescript async function consume(source: unknown) { if (isAsyncIterable(source)) { for await (const item of source) { console.log(item); } } } ``` --- ### `isBigInt` Checks if a value is a bigint. ```typescript import { isBigInt } from '@helpers4/guard'; isBigInt(value: unknown): value is bigint ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is bigint` — True if value is a bigint **Examples:** *isBigInt* ```typescript ```ts isBigInt(42n) // => true isBigInt(42) // => false isBigInt('42') // => false ``` ``` --- ### `isBlob` Checks if a value is a Blob instance. Useful for filtering or type-narrowing in a functional pipeline: `values.filter(isBlob)` ```typescript import { isBlob } from '@helpers4/guard'; isBlob(value: unknown): value is Blob ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Blob` — True if value is a Blob **Examples:** *Detect a Blob* Returns true only for Blob instances. ```typescript isBlob(new Blob(['hello'])) // => true isBlob(new Blob([], { type: 'application/json' })) // => true isBlob('hello') // => false ``` *Filter Blobs from a mixed array* Use as a predicate in .filter() to extract Blob values. ```typescript const values = [new Blob(['a']), 'text', new Blob(['b']), 42]; values.filter(isBlob) // => [Blob, Blob] ``` --- ### `isBoolean` Checks if a value is a boolean. ```typescript import { isBoolean } from '@helpers4/guard'; isBoolean(value: unknown): value is boolean ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is boolean` — True if value is a boolean **Examples:** *isBoolean* ```typescript ```ts isBoolean(true) // => true isBoolean(false) // => true isBoolean(1) // => false ``` ``` --- ### `isCssColor` Checks whether a value is a syntactically-safe, plain CSS color: a hex color (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), a functional notation (`rgb()`, `rgba()`, `hsl()`, `hsla()`), or a single-word named color (`red`, `rebeccapurple`). Intended to sanitize a color value before interpolating it into inline `style`/`cssText` — it does not implement the full CSS color grammar or validate named colors against the real keyword list, it only rejects characters (`{`, `}`, `;`, ``) or shapes that could smuggle extra CSS declarations into the surrounding rule. ```typescript import { isCssColor } from '@helpers4/guard'; isCssColor(value: unknown): value is string ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is string` — `true` if value is a string that looks like a safe, plain CSS color **Examples:** *Validate a color before writing it to inline style* Accepts hex, functional (rgb/rgba/hsl/hsla), and named colors. ```typescript isCssColor('#ff0000') // => true isCssColor('rgba(0, 0, 0, 0.5)') // => true isCssColor('rebeccapurple') // => true isCssColor(42) // => false ``` *Reject values that could inject extra CSS declarations* Guards against untrusted data smuggling a second declaration into a style attribute. ```typescript isCssColor('red; background: url(evil)') // => false isCssColor('red}body{color:blue') // => false ``` --- ### `isDate` Checks if a value is a Date instance. Note: this only checks the type, not whether the Date is valid. Use `date/isValid` to also validate that the Date is not `Invalid Date`. ```typescript import { isDate } from '@helpers4/guard'; isDate(value: unknown): value is Date ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Date` — True if value is a Date instance **Examples:** *isDate* ```typescript ```ts isDate(new Date()) // => true isDate(new Date('invalid')) // => true (still a Date instance) isDate('2023-01-01') // => false isDate(1609459200000) // => false ``` ``` --- ### `isDefined` Checks if a value is defined (not undefined nor null). This is the inverse of isNullish. Use as a type-safe filter callback to remove `null`/`undefined` from arrays. ```typescript import { isDefined } from '@helpers4/guard'; isDefined(value: Maybe): value is T ``` **Parameters:** - `value: Maybe` — The value to check **Returns:** `value is T` — True if value is not undefined nor null **Examples:** *isDefined* ```typescript ```ts isDefined(42) // => true isDefined('') // => true (empty string is defined) isDefined(null) // => false isDefined(undefined) // => false ``` ``` *isDefined* ```typescript ```ts // Type-safe alternative to filter out null/undefined const items: (string | null | undefined)[] = ['a', null, 'b', undefined]; const result = items.filter(isDefined); // => ['a', 'b'] with type string[] ``` ``` --- ### `isError` Checks if a value is an Error instance. ```typescript import { isError } from '@helpers4/guard'; isError(value: unknown): value is Error ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Error` — True if value is an Error (or subclass like TypeError, RangeError, etc.) **Examples:** *isError* ```typescript ```ts isError(new Error('oops')) // => true isError(new TypeError('bad')) // => true isError({ message: 'fake' }) // => false ``` ``` --- ### `isFalsy` Checks if a value is falsy (`false`, `null`, `undefined`, `0`, `""`, `NaN`). ```typescript import { isFalsy } from '@helpers4/guard'; isFalsy(value: unknown): value is Falsy ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Falsy` — True if the value is falsy **Examples:** *Check falsy values* Returns true for all falsy values: false, null, undefined, 0, "", NaN. ```typescript isFalsy(0) // => true isFalsy('') // => true isFalsy(null) // => true isFalsy('hello') // => false ``` --- ### `isFormData` Checks if a value is a FormData instance. Useful for filtering or type-narrowing in a functional pipeline: `values.filter(isFormData)` ```typescript import { isFormData } from '@helpers4/guard'; isFormData(value: unknown): value is FormData ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is FormData` — True if value is a FormData **Examples:** *Detect a FormData* Returns true only for FormData instances. ```typescript isFormData(new FormData()) // => true isFormData({}) // => false isFormData(null) // => false ``` *Filter FormData from a mixed array* Use as a predicate in .filter() to extract FormData values. ```typescript const fd = new FormData(); fd.append('name', 'Alice'); const values = [fd, {}, new FormData(), 'text']; values.filter(isFormData) // => [FormData, FormData] ``` --- ### `isFunction` Checks if a value is a function. ```typescript import { isFunction } from '@helpers4/guard'; isFunction(value: unknown): value is Function ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Function` — True if value is a function **Examples:** *isFunction* ```typescript ```ts isFunction(() => {}) // => true isFunction(function() {}) // => true isFunction('function') // => false ``` ``` --- ### `isGenerator` Checks if a value is a generator object (the result of calling a `function*`). Distinct from isGeneratorFunction: this predicate targets the *instance* produced by calling a generator function, not the function itself. ```typescript import { isGenerator } from '@helpers4/guard'; isGenerator(value: unknown): value is Generator ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Generator` — `true` if value is a Generator instance **Examples:** *Distinguish a generator instance from its function* isGenerator targets the object returned by calling a function*, not the function itself. ```typescript function* counter() { yield 1; yield 2; } isGenerator(counter()) // => true (instance) isGenerator(counter) // => false (function) isGenerator([1, 2]) // => false ``` *Type-narrow to safely call .next()* Narrows the type to Generator so you can call .next() and .return(). ```typescript function* gen() { yield 1; yield 2; } const value: unknown = gen(); if (isGenerator(value)) { const { value: v, done } = value.next(); // v: unknown, done: boolean | undefined } ``` --- ### `isGeneratorFunction` Checks if a value is a generator function (a `function*` declaration or expression). Distinct from isGenerator: this predicate targets the *function* itself, not the iterator it produces when called. ```typescript import { isGeneratorFunction } from '@helpers4/guard'; isGeneratorFunction(value: unknown): value is GeneratorFunction ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is GeneratorFunction` — `true` if value is a GeneratorFunction **Examples:** *Detect a generator function* Returns true for function* declarations and expressions. ```typescript function* gen() { yield 1; } isGeneratorFunction(gen) // => true isGeneratorFunction(gen()) // => false (instance, not function) isGeneratorFunction(() => {}) // => false ``` *Filter generator factories from a mixed array* Use as a predicate to select only generator functions. ```typescript const fns = [() => {}, function* () { yield 1; }, async () => {}]; fns.filter(isGeneratorFunction) // => [function* () { yield 1; }] ``` --- ### `isIterable` Checks if a value is iterable (has a `Symbol.iterator` method). Returns `true` for strings, arrays, Maps, Sets, generators, and any object implementing the iterable protocol. ```typescript import { isIterable } from '@helpers4/guard'; isIterable(value: unknown): value is Iterable ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Iterable` — 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/guard'; isMap(value: unknown): value is Map ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Map` — True if value is a Map **Examples:** *isMap* ```typescript ```ts isMap(new Map()) // => true isMap(new Map([['a', 1]])) // => true isMap({}) // => false ``` ``` --- ### `isNull` Checks if a value is `null`. ```typescript import { isNull } from '@helpers4/guard'; isNull(value: unknown): value is null ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is null` — True if value is null **Examples:** *isNull* ```typescript ```ts isNull(null) // => true isNull(undefined) // => false isNull(0) // => false ``` ``` --- ### `isNullish` Checks if a value is null or undefined (nullish). ```typescript import { isNullish } from '@helpers4/guard'; isNullish(value: unknown): value is null | undefined ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is null | undefined` — True if value is null or undefined **Examples:** *Check for null or undefined* Returns true only for null and undefined, not other falsy values. ```typescript isNullish(null) // => true isNullish(undefined) // => true isNullish(0) // => false isNullish('') // => false ``` *Guard before accessing properties* Use as a type guard to safely narrow types. ```typescript function greet(name: string | null | undefined): string { if (isNullish(name)) return 'Hello, stranger!'; return `Hello, ${name}!`; } greet(null) // => 'Hello, stranger!' ``` --- ### `isNumber` Checks if a value is a number. Returns `false` for `NaN`, which intentionally deviates from `typeof` behavior to increase user-friendliness. ```typescript import { isNumber } from '@helpers4/guard'; isNumber(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — True if value is a number (excludes NaN) **Examples:** *isNumber* ```typescript ```ts isNumber(42) // => true isNumber(0) // => true isNumber(NaN) // => false isNumber('123') // => false ``` ``` --- ### `isPlainObject` Checks if a value is a plain object. A plain object is created by `{}`, `new Object()`, or `Object.create(null)`. Returns `false` for arrays, Date, Map, Set, RegExp, class instances, etc. ```typescript import { isPlainObject } from '@helpers4/guard'; isPlainObject(value: unknown): value is Record ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Record` — True if value is a plain object **Examples:** *isPlainObject* ```typescript ```ts isPlainObject({}) // => true isPlainObject({ a: 1 }) // => true isPlainObject(new Date()) // => false isPlainObject([]) // => false isPlainObject(null) // => false ``` ``` --- ### `isPrimitive` Checks if a value is a JavaScript primitive. Primitive types: `string`, `number`, `boolean`, `bigint`, `symbol`, `null`, `undefined`. ```typescript import { isPrimitive } from '@helpers4/guard'; isPrimitive(value: unknown): value is Primitive ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Primitive` — True if value is a primitive **Examples:** *isPrimitive* ```typescript ```ts isPrimitive('hello') // => true isPrimitive(42) // => true isPrimitive(null) // => true isPrimitive({}) // => false isPrimitive([]) // => false ``` ``` --- ### `isPromise` Checks if a value is a Promise or a thenable. Returns `true` for any object that has `.then()` and `.catch()` methods, including native Promises and userland implementations. ```typescript import { isPromise } from '@helpers4/guard'; isPromise(value: unknown): value is PromiseLike ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PromiseLike` — True if value is a Promise-like object **Examples:** *isPromise* ```typescript ```ts isPromise(Promise.resolve(42)) // => true isPromise(new Promise(() => {})) // => true isPromise({ then: () => {} }) // => false (no .catch) isPromise(42) // => false ``` ``` --- ### `isPromiseLike` Checks if a value is a thenable (has a `.then()` method). Looser than isPromise: accepts any object or function with a `then` method, including non-standard Promise implementations without `.catch()`. Follows the Promise/A+ specification for thenables. ```typescript import { isPromiseLike } from '@helpers4/guard'; isPromiseLike(value: unknown): value is PromiseLike ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PromiseLike` — `true` if value is a PromiseLike (thenable) **Examples:** *Detect any thenable* Returns true for native Promises and any object with a .then() method. ```typescript isPromiseLike(Promise.resolve(1)) // => true isPromiseLike({ then: () => {} }) // => true (thenable) isPromiseLike(42) // => false isPromiseLike(null) // => false isPromiseLike({ then: 'not-a-fn' }) // => false ``` *Handle both Promises and thenables in a utility* Use isPromiseLike to accept any thenable, not just native Promises. ```typescript function toPromise(value: T | PromiseLike): Promise { if (isPromiseLike(value)) return Promise.resolve(value); return Promise.resolve(value); } ``` --- ### `isPropertyKey` Checks if a value is a valid property key: `string`, `number`, or `symbol`. ```typescript import { isPropertyKey } from '@helpers4/guard'; isPropertyKey(value: unknown): value is PropertyKey ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PropertyKey` — `true` if value can be used as an object property key **Examples:** *Detect valid property keys* Strings, numbers, and symbols are valid property keys. ```typescript isPropertyKey('name') // => true isPropertyKey(42) // => true isPropertyKey(Symbol('id')) // => true isPropertyKey(null) // => false isPropertyKey(true) // => false ``` *Safe dynamic property access* Use as a guard before indexing an object with an unknown key. ```typescript function get(obj: Record, key: unknown): unknown { if (isPropertyKey(key)) return obj[key]; return undefined; } get({ a: 1 }, 'a') // => 1 get({ a: 1 }, null) // => undefined ``` --- ### `isRegExp` Checks if a value is a RegExp instance. ```typescript import { isRegExp } from '@helpers4/guard'; isRegExp(value: unknown): value is RegExp ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is RegExp` — True if value is a RegExp **Examples:** *isRegExp* ```typescript ```ts isRegExp(/abc/) // => true isRegExp(new RegExp('a')) // => true isRegExp('abc') // => false ``` ``` --- ### `isSet` Checks if a value is a Set instance. ```typescript import { isSet } from '@helpers4/guard'; isSet(value: unknown): value is Set ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Set` — True if value is a Set **Examples:** *Check whether a value is a Set* Returns true only for real Set instances. ```typescript isSet(new Set([1, 2, 3])) // => true isSet([1, 2, 3]) // => false ``` *Type-safe narrowing in a conditional* Inside the `if` branch, the value is narrowed to Set. ```typescript const value: unknown = new Set(['a', 'b']); if (isSet(value)) { value.size // => 2, type-safe access } ``` --- ### `isSpecialObject` Determines if a value is a special object that should not have its properties compared deeply. Special objects include: Date, Function, Promise, Observable, RegExp, Error, Map, Set, WeakMap, WeakSet, etc. ```typescript import { isSpecialObject } from '@helpers4/guard'; isSpecialObject(value: unknown): boolean ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `boolean` — `true` if the value is a special object, `false` otherwise **Examples:** *Detect special objects* Returns true for built-in objects like Date, Map, Set, RegExp, etc. ```typescript isSpecialObject(new Date()) // => true isSpecialObject(new Map()) // => true isSpecialObject(/regex/) // => true ``` *Plain objects are not special* Returns false for plain objects and arrays. ```typescript isSpecialObject({ a: 1 }) // => false isSpecialObject([1, 2]) // => false ``` --- ### `isString` Checks if a value is a string. ```typescript import { isString } from '@helpers4/guard'; isString(value: unknown): value is string ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is string` — True if value is a string **Examples:** *isString* ```typescript ```ts isString('hello') // => true isString(123) // => false ``` ``` --- ### `isSymbol` Checks if a value is a symbol. ```typescript import { isSymbol } from '@helpers4/guard'; isSymbol(value: unknown): value is symbol ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is symbol` — True if value is a symbol **Examples:** *isSymbol* ```typescript ```ts isSymbol(Symbol('test')) // => true isSymbol(Symbol.iterator) // => true isSymbol('symbol') // => false ``` ``` --- ### `isTemporalDuration` Checks if a value is a `Temporal.Duration`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalDuration } from '@helpers4/guard'; isTemporalDuration(value: unknown): value is Duration ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Duration` — True if value is a `Temporal.Duration` **Examples:** *isTemporalDuration* ```typescript ```ts isTemporalDuration(Temporal.Duration.from({ hours: 1 })) // => true isTemporalDuration(Temporal.Now.instant()) // => false isTemporalDuration(1000) // => false ``` ``` --- ### `isTemporalInstant` Checks if a value is a `Temporal.Instant`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalInstant } from '@helpers4/guard'; isTemporalInstant(value: unknown): value is Instant ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Instant` — True if value is a `Temporal.Instant` **Examples:** *isTemporalInstant* ```typescript ```ts isTemporalInstant(Temporal.Now.instant()) // => true isTemporalInstant(Temporal.Now.plainDateISO()) // => false isTemporalInstant(new Date()) // => false ``` ``` --- ### `isTemporalPlainDate` Checks if a value is a `Temporal.PlainDate`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalPlainDate } from '@helpers4/guard'; isTemporalPlainDate(value: unknown): value is PlainDate ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PlainDate` — True if value is a `Temporal.PlainDate` **Examples:** *isTemporalPlainDate* ```typescript ```ts isTemporalPlainDate(Temporal.PlainDate.from('2025-01-19')) // => true isTemporalPlainDate(Temporal.Now.instant()) // => false isTemporalPlainDate(new Date()) // => false ``` ``` --- ### `isTemporalPlainDateTime` Checks if a value is a `Temporal.PlainDateTime`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalPlainDateTime } from '@helpers4/guard'; isTemporalPlainDateTime(value: unknown): value is PlainDateTime ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PlainDateTime` — True if value is a `Temporal.PlainDateTime` **Examples:** *isTemporalPlainDateTime* ```typescript ```ts isTemporalPlainDateTime(Temporal.PlainDateTime.from('2025-01-19T12:00')) // => true isTemporalPlainDateTime(Temporal.Now.instant()) // => false isTemporalPlainDateTime(new Date()) // => false ``` ``` --- ### `isTemporalPlainTime` Checks if a value is a `Temporal.PlainTime`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalPlainTime } from '@helpers4/guard'; isTemporalPlainTime(value: unknown): value is PlainTime ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is PlainTime` — True if value is a `Temporal.PlainTime` **Examples:** *isTemporalPlainTime* ```typescript ```ts isTemporalPlainTime(Temporal.PlainTime.from('12:30:00')) // => true isTemporalPlainTime(Temporal.Now.instant()) // => false isTemporalPlainTime(new Date()) // => false ``` ``` --- ### `isTemporalZonedDateTime` Checks if a value is a `Temporal.ZonedDateTime`. Uses `instanceof` when `Temporal` is available globally, and falls back to `Symbol.toStringTag` for environments without Temporal (e.g. browsers). ```typescript import { isTemporalZonedDateTime } from '@helpers4/guard'; isTemporalZonedDateTime(value: unknown): value is ZonedDateTime ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is ZonedDateTime` — True if value is a `Temporal.ZonedDateTime` **Examples:** *isTemporalZonedDateTime* ```typescript ```ts isTemporalZonedDateTime(Temporal.Now.zonedDateTimeISO()) // => true isTemporalZonedDateTime(Temporal.Now.instant()) // => false isTemporalZonedDateTime(new Date()) // => false ``` ``` --- ### `isTimestamp` Checks if a value is a valid timestamp (milliseconds or Unix seconds). Supports: - JavaScript / Java timestamps (milliseconds since epoch) - Unix timestamps (seconds since epoch) The function uses a heuristic to distinguish between the two: numbers ≤ ~7.26 billion are treated as seconds, larger as milliseconds. ```typescript import { isTimestamp } from '@helpers4/guard'; isTimestamp(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — True if value is a number that represents a valid timestamp **Examples:** *isTimestamp* ```typescript ```ts isTimestamp(1609459200000) // => true (JS ms — 2021-01-01) isTimestamp(1609459200) // => true (Unix seconds — 2021-01-01) isTimestamp(Date.now()) // => true isTimestamp(NaN) // => false isTimestamp('1609459200') // => false (not a number) ``` ``` --- ### `isTruthy` Checks if a value is truthy (not `false`, `null`, `undefined`, `0`, `""`, or `NaN`). This is the type-safe alternative to `Boolean()` as a filter callback. Unlike `filter(Boolean)`, using `filter(isTruthy)` correctly narrows the resulting array type by excluding falsy values. ```typescript import { isTruthy } from '@helpers4/guard'; isTruthy(value: Falsy | T): value is T ``` **Parameters:** - `value: Falsy | T` — The value to check **Returns:** `value is T` — True if the value is truthy **Examples:** *Check truthy values* Returns true for all truthy values, false for falsy ones. ```typescript isTruthy(1) // => true isTruthy('hello') // => true isTruthy(0) // => false isTruthy(null) // => false ``` *Type-safe filter alternative to Boolean* Use isTruthy with Array.filter to get correct TypeScript narrowing. ```typescript const items = ['a', '', null, 'b', undefined]; const result = items.filter(isTruthy); // => ['a', 'b'] with type string[] ``` --- ### `isUndefined` Checks if a value is `undefined`. ```typescript import { isUndefined } from '@helpers4/guard'; isUndefined(value: unknown): value is undefined ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is undefined` — True if value is undefined **Examples:** *isUndefined* ```typescript ```ts isUndefined(undefined) // => true isUndefined(null) // => false isUndefined(0) // => false ``` ``` --- ### `isValidRegex` Checks if a string is a valid regex pattern. ```typescript import { isValidRegex } from '@helpers4/guard'; isValidRegex(value: string): boolean ``` **Parameters:** - `value: string` — The string to check **Returns:** `boolean` — True if the string is a valid regex pattern **Examples:** *isValidRegex* ```typescript ```ts isValidRegex('[a-z]+') // => true isValidRegex('.*') // => true isValidRegex('[') // => false ``` ``` --- ### `isWeakMap` Checks if a value is a WeakMap instance. ```typescript import { isWeakMap } from '@helpers4/guard'; isWeakMap(value: unknown): value is WeakMap ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is WeakMap` — True if value is a WeakMap **Examples:** *Check whether a value is a WeakMap* Distinguishes WeakMap from the regular (iterable) Map. ```typescript isWeakMap(new WeakMap()) // => true isWeakMap(new Map()) // => false ``` *Plain objects are not WeakMaps* Only real WeakMap instances pass, not objects that merely look like one. ```typescript isWeakMap({ get: () => undefined, set: () => {} }) // => false ``` --- ### `isWeakSet` Checks if a value is a WeakSet instance. ```typescript import { isWeakSet } from '@helpers4/guard'; isWeakSet(value: unknown): value is WeakSet ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is WeakSet` — True if value is a WeakSet **Examples:** *Check whether a value is a WeakSet* Distinguishes WeakSet from the regular (iterable) Set. ```typescript isWeakSet(new WeakSet()) // => true isWeakSet(new Set()) // => false ``` *Arrays are not WeakSets* Only real WeakSet instances pass. ```typescript isWeakSet([1, 2, 3]) // => false ``` --- ## id Package: `@helpers4/id` ### `uuid7` Generates a UUID v7 string (RFC 9562). UUID v7 embeds a Unix timestamp in milliseconds, making it chronologically sortable while retaining randomness. ```typescript import { uuid7 } from '@helpers4/id'; uuid7(): string ``` **Returns:** `string` — A UUID v7 string in the format `xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx` **Examples:** *Generate a UUID v7* Produces a RFC 9562 UUID v7 string with an embedded millisecond timestamp. ```typescript uuid7() // => "019077e0-5c70-7b3a-8a1f-3e4d5b6c7d8e" ``` *UUIDs are chronologically sortable* UUID v7 values generated later are lexicographically greater, making them ideal for database primary keys. ```typescript const id1 = uuid7(); // ... later ... const id2 = uuid7(); id1 < id2 // => true ``` *Each UUID is unique* No two calls produce the same value. ```typescript uuid7() !== uuid7() // => true ``` --- ## markdown Package: `@helpers4/markdown` ### `escape` Escapes all Markdown special characters in a string so they render as literal text rather than formatting syntax. Escaped characters: `\ \` * _ { } [ ] ( ) # + - . !` Pass `{ cell: true }` to also escape pipe characters and replace newlines with spaces, making the result safe for embedding in a Markdown table cell. ```typescript import { escape } from '@helpers4/markdown'; escape(str: string, options?: EscapeOptions): string ``` **Parameters:** - `str: string` — The raw string to escape - `options?: EscapeOptions` — Optional escaping options **Returns:** `string` — The escaped string **Examples:** *Escape special Markdown characters* Prefixes every Markdown special character with a backslash. ```typescript escape('**bold** and _italic_') // => '\\*\\*bold\\*\\* and \\_italic\\_' ``` *Safely render user input inside Markdown* Prevents user-supplied strings from breaking Markdown formatting. ```typescript const userInput = '(C) [helpers4]'; const safe = escape(userInput); // => '\\(C\\) \\[helpers4\\]' ``` --- ## node Package: `@helpers4/node` ### `isBuffer` Checks if a value is a Node.js Buffer instance. `Buffer` extends `Uint8Array` and is specific to Node.js, Bun, and Deno. In browser-only environments where `Buffer` is not defined, this function always returns `false`. Useful for filtering or type-narrowing in a functional pipeline: `values.filter(isBuffer)` ```typescript import { isBuffer } from '@helpers4/node'; isBuffer(value: unknown): value is Buffer ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Buffer` — True if value is a Buffer **Examples:** *Detect a Node.js Buffer* Returns true only for Buffer instances. Uint8Array is not a Buffer. ```typescript isBuffer(Buffer.from('hello')) // => true isBuffer(new Uint8Array(8)) // => false isBuffer('hello') // => false ``` *Filter Buffers from a mixed array* Use as a predicate in .filter() to extract Buffer values. ```typescript const values = [Buffer.from('a'), 'text', Buffer.alloc(4), 42]; values.filter(isBuffer) // => [Buffer, Buffer] ``` --- ### `isNodeStream` Checks if a value is a Node.js stream (has a `.pipe()` method). Uses duck-typing: any object with a `pipe` function qualifies, covering `Readable`, `Writable`, `Duplex`, `Transform`, and custom stream-compatible objects without importing from `node:stream`. ```typescript import { isNodeStream } from '@helpers4/node'; isNodeStream(value: unknown): value is object ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is object` — `true` if value is a Node.js stream **Examples:** *Detect a Node.js stream* Returns true for any object with a .pipe() method (Readable, Writable, Transform, etc.). ```typescript import { Readable } from 'node:stream'; isNodeStream(new Readable({ read() {} })) // => true isNodeStream({}) // => false isNodeStream(null) // => false ``` *Guard before piping an unknown value* Use isNodeStream to safely pipe only known streams. ```typescript import { Writable } from 'node:stream'; function pipeToOutput(source: unknown, dest: Writable): void { if (isNodeStream(source)) { source.pipe(dest); } } ``` --- ### `isSharedArrayBuffer` Checks if a value is a `SharedArrayBuffer` instance. `SharedArrayBuffer` enables shared memory between the main thread and worker threads. In browsers without COOP/COEP headers, `SharedArrayBuffer` may be unavailable; this function returns `false` in that case. ```typescript import { isSharedArrayBuffer } from '@helpers4/node'; isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is SharedArrayBuffer` — `true` if value is a SharedArrayBuffer **Examples:** *Distinguish SharedArrayBuffer from ArrayBuffer* Returns true only for SharedArrayBuffer instances, not plain ArrayBuffers. ```typescript isSharedArrayBuffer(new SharedArrayBuffer(8)) // => true isSharedArrayBuffer(new ArrayBuffer(8)) // => false isSharedArrayBuffer(null) // => false ``` *Safe shared memory check before worker communication* Use as a guard to ensure a buffer can be transferred to a Worker. ```typescript function sendToWorker(buffer: unknown): void { if (isSharedArrayBuffer(buffer)) { // buffer is SharedArrayBuffer — can be shared directly // worker.postMessage({ buffer }); } else { // must transfer or copy } } ``` --- ## number Package: `@helpers4/number` ### `clamp` Clamps a number between min and max values ```typescript import { clamp } from '@helpers4/number'; clamp(value: number, min: number, max: number): number ``` **Parameters:** - `value: number` — The value to clamp - `min: number` — Minimum value - `max: number` — Maximum value **Returns:** `number` — Clamped value **Examples:** *Clamp a value within range* Restricts a number to be within a min/max range. ```typescript clamp(15, 0, 10) // => 10 clamp(-5, 0, 10) // => 0 clamp(5, 0, 10) // => 5 ``` --- ### `correctFloat` Corrects floating-point arithmetic errors by rounding to a given number of significant digits. Useful after calculations that accumulate binary floating-point drift (e.g. `0.1 + 0.2 === 0.30000000000000004`). The default precision of 14 significant digits eliminates typical rounding noise for values in the range used by most applications. Note: for values whose integer part already consumes 14 or more digits (i.e. |value| ≥ 1e13), toPrecision(14) has no room left for decimal digits and will silently truncate them. Increase `precision` if you need to correct drift in very large numbers. Note: IEEE-754 doubles carry at most ~17 significant decimal digits. Precision values above 17 pad with digits that reflect the underlying binary representation rather than correcting drift. ```typescript import { correctFloat } from '@helpers4/number'; correctFloat(value: number, precision: number): number ``` **Parameters:** - `value: number` — The floating-point value to correct - `precision: number` (default: `14`) — Integer number of significant digits between 1 and 100 (default: 14). Values above 17 are valid but expose binary noise beyond IEEE-754's meaningful range. **Returns:** `number` — The corrected value **Examples:** *Fix floating-point drift* Corrects the classic 0.1 + 0.2 floating-point arithmetic error. ```typescript 0.1 + 0.2 // => 0.30000000000000004 correctFloat(0.1 + 0.2) // => 0.3 ``` *Custom significant-digit precision* Pass a second argument to control how many significant digits to keep. ```typescript correctFloat(1.23456789, 4) // => 1.235 correctFloat(1.23456789, 6) // => 1.23457 ``` --- ### `extractNumber` Extracts the first number embedded anywhere in a string, or passes through a `number`. Unlike a plain `parseFloat`/`parseInt`, the number does not need to be at the start of the string: digits are searched for anywhere, so leading/trailing text (units, labels, ...) is ignored. A `-` before the digits and a scientific-notation suffix (`e`/`E`) are disambiguated with ExtractNumberOptions.sign and ExtractNumberOptions.exponent. Returns `undefined` if no number can be found. ```typescript import { extractNumber } from '@helpers4/number'; extractNumber(value: unknown, options: ExtractNumberOptions): number | undefined ``` **Parameters:** - `value: unknown` — The value to extract a number from - `options: ExtractNumberOptions` (default: `{}`) — Options controlling sign and exponent disambiguation **Returns:** `number | undefined` — The extracted number, or `undefined` if none was found **Examples:** *extractNumber* ```typescript ```ts extractNumber('16.5px') // => 16.5 extractNumber('.5rem') // => 0.5 (leading-dot decimal) extractNumber('-.5') // => -0.5 (leading-dot with sign) extractNumber('Wafer 10') // => 10 extractNumber('xxx-111') // => 111 ('-' glued to text → separator) extractNumber('xxx -111') // => -111 ('-' preceded by a space → sign) extractNumber('x-.5') // => 0.5 ('-' glued to 'x' → separator; leading-dot decimal follows) extractNumber('-111') // => -111 ('-' at the start of the string → sign) extractNumber('1e5 mol') // => 100000 extractNumber('1e5kg') // => 1 ('e5' glued to text → mantissa only) extractNumber('no number') // => undefined extractNumber(42) // => 42 ``` ``` --- ### `formatCompact` Formats a number using compact notation (e.g. `1_500_000 → "1.5M"`). Thin wrapper over `Intl.NumberFormat` with `notation: 'compact'`. Companion of `formatSize` in the same `format*` family. ```typescript import { formatCompact } from '@helpers4/number'; formatCompact(value: number, locale?: string): string ``` **Parameters:** - `value: number` — The number to format. - `locale?: string` — BCP 47 locale tag. Defaults to the runtime locale. **Returns:** `string` — A compact string representation of the number. **Examples:** *Compact large numbers* Formats a number using K / M suffixes for readability. ```typescript formatCompact(1_500_000, 'en') // => '1.5M' formatCompact(1_000, 'en') // => '1K' formatCompact(999, 'en') // => '999' ``` *Locale-aware formatting* Uses the provided locale for the decimal separator and suffix. ```typescript formatCompact(1_500_000, 'fr') // => '1,5 M' ``` --- ### `formatSize` Format a byte count into a human-readable string with the appropriate unit. Each unit is 1024 of the previous (binary prefix). The result is formatted with one decimal place. ```typescript import { formatSize } from '@helpers4/number'; formatSize(bytes: number): string ``` **Parameters:** - `bytes: number` — A non-negative integer representing a byte count. **Returns:** `string` — A human-readable string such as `'0.0B'`, `'1.5KB'`, `'3.2MB'`. **Examples:** *Format bytes to human-readable size* Converts a raw byte count to a human-readable string using binary prefixes. ```typescript formatSize(0) // '0.0B' formatSize(512) // '512.0B' formatSize(1024) // '1.0KB' formatSize(1_048_576) // '1.0MB' formatSize(1_073_741_824) // '1.0GB' ``` --- ### `inRange` Checks whether a number falls within `[min, max]` (both inclusive by default). ```typescript import { inRange } from '@helpers4/number'; inRange(value: number, min: number, max: number, options: InRangeOptions): boolean ``` **Parameters:** - `value: number` — The number to test - `min: number` — Lower bound - `max: number` — Upper bound - `options: InRangeOptions` (default: `{}`) — Boundary inclusion mode (default: `'both'`) **Returns:** `boolean` — `true` if `value` is within the specified range **Examples:** *Check if a value is within bounds (inclusive)* Both min and max are included by default. ```typescript inRange(5, 1, 10) // => true inRange(0, 1, 10) // => false inRange(1, 1, 10) // => true (min included) inRange(10, 1, 10) // => true (max included) ``` *Exclusive range* Use { inclusive: "none" } for open interval (min, max). ```typescript inRange(5, 1, 10, { inclusive: 'none' }) // => true inRange(1, 1, 10, { inclusive: 'none' }) // => false inRange(10, 1, 10, { inclusive: 'none' }) // => false ``` --- ### `isEven` Checks if a value is an even integer. Returns `false` for non-numbers, non-integers, `NaN`, `Infinity`, and odd integers. ```typescript import { isEven } from '@helpers4/number'; isEven(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — `true` if value is an integer divisible by 2 **Examples:** *Check if a number is even* Returns true for integers divisible by 2, false otherwise. ```typescript isEven(4) // => true isEven(0) // => true isEven(3) // => false isEven(1.5) // => false (not an integer) ``` *Filter even numbers from an array* Use as a predicate in .filter() to extract even integers. ```typescript const nums = [1, 2, 3, 4, 5, 6]; nums.filter(isEven) // => [2, 4, 6] ``` --- ### `isNegative` Checks if a value is a number less than 0. Returns `false` for `NaN`, `0`, positive numbers, and non-number types. ```typescript import { isNegative } from '@helpers4/number'; isNegative(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — True if value is a negative number **Examples:** *isNegative* ```typescript ```ts isNegative(-1) // => true isNegative(-0.5) // => true isNegative(-Infinity) // => true isNegative(0) // => false isNegative(1) // => false isNegative(NaN) // => false ``` ``` --- ### `isOdd` Checks if a value is an odd integer. Returns `false` for non-numbers, non-integers, `NaN`, `Infinity`, and even integers. ```typescript import { isOdd } from '@helpers4/number'; isOdd(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — `true` if value is an integer not divisible by 2 **Examples:** *Check if a number is odd* Returns true for integers not divisible by 2, false otherwise. ```typescript isOdd(3) // => true isOdd(1) // => true isOdd(2) // => false isOdd(0) // => false isOdd(1.5) // => false (not an integer) ``` *Filter odd numbers from an array* Use as a predicate in .filter() to extract odd integers. ```typescript const nums = [1, 2, 3, 4, 5, 6]; nums.filter(isOdd) // => [1, 3, 5] ``` --- ### `isPositive` Checks if a value is a number greater than 0. Returns `false` for `NaN`, `0`, negative numbers, and non-number types. ```typescript import { isPositive } from '@helpers4/number'; isPositive(value: unknown): value is number ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is number` — True if value is a positive number **Examples:** *isPositive* ```typescript ```ts isPositive(42) // => true isPositive(0.1) // => true isPositive(Infinity) // => true isPositive(0) // => false isPositive(-1) // => false isPositive(NaN) // => false ``` ``` --- ### `lerp` Linearly interpolates between `start` and `end` by the factor `t`. - `t = 0` returns `start`. - `t = 1` returns `end`. - Values of `t` outside `[0, 1]` extrapolate beyond the range. ```typescript import { lerp } from '@helpers4/number'; lerp(start: number, end: number, t: number): number ``` **Parameters:** - `start: number` — The start value. - `end: number` — The end value. - `t: number` — The interpolation factor. **Returns:** `number` — The interpolated value. **Examples:** *Interpolate between two values* Returns the value between start and end at position t (0 = start, 1 = end). ```typescript lerp(0, 100, 0) // => 0 lerp(0, 100, 0.5) // => 50 lerp(0, 100, 1) // => 100 ``` *Animate a colour channel* t outside [0, 1] extrapolates beyond the range. ```typescript lerp(0, 255, 0.5) // => 127.5 lerp(0, 10, 2) // => 20 (extrapolation) ``` --- ### `randomBetween` Generates a random number between min and max (inclusive) ```typescript import { randomBetween } from '@helpers4/number'; randomBetween(min: number, max: number): number ``` **Parameters:** - `min: number` — Minimum value - `max: number` — Maximum value **Returns:** `number` — Random number between min and max **Examples:** *Generate a random float in range* Returns a random number between min and max (inclusive). ```typescript randomBetween(1, 10) // => e.g. 5.327... ``` *Generate a random integer in range* Returns a random integer between min and max (inclusive). ```typescript randomIntBetween(1, 6) // => e.g. 4 ``` --- ### `randomIntBetween` Generates a random integer between min and max (inclusive) ```typescript import { randomIntBetween } from '@helpers4/number'; randomIntBetween(min: number, max: number): number ``` **Parameters:** - `min: number` — Minimum value - `max: number` — Maximum value **Returns:** `number` — Random integer between min and max --- ### `roundTo` Rounds a number to specified decimal places ```typescript import { roundTo } from '@helpers4/number'; roundTo(value: number, decimals: number): number ``` **Parameters:** - `value: number` — The number to round - `decimals: number` — Number of decimal places **Returns:** `number` — Rounded number **Examples:** *Round to 2 decimal places* Rounds a floating-point number to the specified number of decimals. ```typescript roundTo(3.14159, 2) // => 3.14 ``` *Round to 0 decimal places* Effectively rounds to the nearest integer. ```typescript roundTo(3.7, 0) // => 4 ``` --- ## object Package: `@helpers4/object` ### `clone` Creates a shallow copy of a value — one level deep, unlike cloneDeep. Unlike a plain `{ ...value }` spread, this correctly reconstructs `Date`, `Map`, `Set`, and arrays instead of producing an empty (or wrong-shaped) plain object for them. Primitives are returned as-is. Any other object (including class instances not listed above) has its own enumerable string keys shallow-copied into a plain object — the same fallback `cloneDeep` uses, so the two stay consistent for types neither one special-cases. ```typescript import { clone } from '@helpers4/object'; clone(value: T): T ``` **Parameters:** - `value: T` — The value to shallow-clone **Returns:** `T` — A shallow copy of `value` **Examples:** *Shallow-copy a plain object* Top-level keys are copied; nested values still share references (shallow). ```typescript const obj = { a: 1, b: { c: 2 } }; const copy = clone(obj); copy.b === obj.b // => true (same nested reference) ``` *Correctly clones Date/Map/Set, unlike a spread* { ...new Date() } produces {} — clone() doesn't have that problem. ```typescript clone(new Map([['a', 1]])) // => new Map with the same entries, not {} ``` --- ### `cloneDeep` Creates a deep copy of an object or array. Recursively clones own enumerable string keys. `Date` instances are reconstructed with the same epoch value. Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are silently skipped. Does **not** handle circular references. **Limitation:** symbol-keyed own properties are **not** copied — only string keys are processed. Use `structuredClone` if symbol propagation is required. ```typescript import { cloneDeep } from '@helpers4/object'; cloneDeep(obj: T): T ``` **Parameters:** - `obj: T` — The value to clone **Returns:** `T` — A deep copy of `obj` **Examples:** *Clone a nested object* Creates a deep copy — modifying the clone does not affect the original. ```typescript const original = { a: { b: 1 } }; const cloned = cloneDeep(original); cloned.a.b = 2; // original.a.b is still 1 ``` --- ### `compact` Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object. Own keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { compact } from '@helpers4/object'; compact>(obj: T): Partial ``` **Parameters:** - `obj: T` — The source object **Returns:** `Partial` — A new object containing only entries with truthy values ```typescript import { compact } from '@helpers4/object'; compact(obj: undefined): undefined ``` **Parameters:** - `obj: undefined` — The source object **Returns:** `undefined` — A new object containing only entries with truthy values ```typescript import { compact } from '@helpers4/object'; compact(obj: null): null ``` **Parameters:** - `obj: null` — The source object **Returns:** `null` — A new object containing only entries with truthy values **Examples:** *Remove falsy values from object* Removes all entries with falsy values (false, null, undefined, 0, "", NaN). ```typescript compact({ a: 1, b: null, c: '', d: 0, e: 'hello' }) // => { a: 1, e: 'hello' } ``` *Clean up API response* Useful to strip empty or missing fields before sending data. ```typescript compact({ name: 'Alice', email: '', age: 0, role: 'admin' }) // => { name: 'Alice', role: 'admin' } ``` --- ### `diff` Structural object diff. Returns `true` when both inputs are deeply equal, otherwise a DiffResult describing the differences key by key. Comparison rules: - Same reference \u2192 `true`. - Either side is `null`/`undefined` (and not both) \u2192 `false`. - Both `Date` \u2192 epoch comparison. - Both arrays \u2192 compared with `array/equalsDeep` (leaf, no diff drill-down). - Special objects (Map, Set, RegExp, Promise, class instances\u2026) \u2192 reference equality. - Plain objects \u2192 key-by-key, recursing up to `options.depth` levels. - Mixed types (e.g. array vs object, Date vs object) \u2192 `false`. For a boolean wrapper see equalsDeep from this category. For a one-level boolean check see equalsShallow from this category. ```typescript import { diff } from '@helpers4/object'; diff(objA: object | null | undefined, objB: object | null | undefined, options: DiffOptions): boolean | DiffResult ``` **Parameters:** - `objA: object | null | undefined` — First value (object, `null`, or `undefined`). - `objB: object | null | undefined` — Second value (object, `null`, or `undefined`). - `options: DiffOptions` (default: `{}`) — See DiffOptions. **Returns:** `boolean | DiffResult` — `true` when equal, otherwise a DiffResult, or `false` for incompatible types. **Examples:** *Compare nested objects* Deeply compares two objects, returning true when they are structurally equal. ```typescript diff({ a: { b: 1 } }, { a: { b: 1 } }) // => true ``` *Detect deep differences* Returns a detailed diff object when nested values differ. ```typescript diff({ a: { b: 1 } }, { a: { b: 2 } }) // => { a: { b: false } } ``` --- ### `equalsDeep` Recursive structural object equality. Boolean wrapper around diff \u2014 returns `true` when the two values are deeply equal according to the same rules. Use this when you only need a yes/no answer; use diff when you also need to know *what* differs. For a one-level boolean check use equalsShallow. ```typescript import { equalsDeep } from '@helpers4/object'; equalsDeep(objA: object | null | undefined, objB: object | null | undefined): boolean ``` **Parameters:** - `objA: object | null | undefined` — First value (object, `null`, or `undefined`). - `objB: object | null | undefined` — Second value (object, `null`, or `undefined`). **Returns:** `boolean` — `true` if both inputs are deeply equal, `false` otherwise. **Examples:** *Compare nested objects* Recursive structural equality. Returns true when the two values are deeply equal. ```typescript equalsDeep({ a: { b: 1 } }, { a: { b: 1 } }) // => true ``` *Detect deep differences* Returns false when nested values differ. ```typescript equalsDeep({ a: { b: 1 } }, { a: { b: 2 } }) // => false ``` --- ### `equalsShallow` One-level (shallow) object equality. Two objects are equal when they share the exact same set of own enumerable string keys and each pair of values satisfies strict equality (`===`). No recursion: nested objects/arrays are compared by reference. Falls back to strict equality when either input is `null`, `undefined` or not an object \u2014 so primitives match if and only if they are `===`. Arrays are not supported; they always return `false` (unless identical references). Use `array/equalsShallow` instead. For recursive structural comparison use equalsDeep. For a diff structure use diff. ```typescript import { equalsShallow } from '@helpers4/object'; equalsShallow(objA: unknown, objB: unknown): boolean ``` **Parameters:** - `objA: unknown` — First value to compare - `objB: unknown` — Second value to compare **Returns:** `boolean` — `true` if values are shallowly equal, `false` otherwise. **Examples:** *Compare two equal objects* Uses JSON.stringify for a fast comparison. ```typescript equalsShallow({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true ``` --- ### `flatten` Flattens a nested object into a single-level object whose keys are the dot-notation path to each leaf value. The inverse of unflatten. Only **plain objects** are recursed into — arrays, `Date`, `Map`, `RegExp`, class instances, and empty plain objects `{}` are kept as opaque leaf values. This keeps `flatten`/`unflatten` a clean, invertible pair: arrays can't be losslessly told apart from plain objects once reduced to dotted keys, so this implementation doesn't attempt it. Caveat shared by every dotted-path flattening scheme: a key that itself contains a literal `.` is indistinguishable from real nesting once flattened (`{ 'a.b': 1 }` and `{ a: { b: 1 } }` both produce `{ 'a.b': 1 }`). ```typescript import { flatten } from '@helpers4/object'; flatten(obj: Record): Record ``` **Parameters:** - `obj: Record` — The object to flatten **Returns:** `Record` — A single-level object with dot-notation keys **Examples:** *Flatten a nested config object* Each key in the result is the full dot-notation path to a leaf value. ```typescript flatten({ server: { host: 'localhost', port: 8080 } }) // => { 'server.host': 'localhost', 'server.port': 8080 } ``` *Arrays and special objects are kept as leaves* Only plain objects are recursed into — arrays stay intact. ```typescript flatten({ tags: ['a', 'b'], meta: { owner: 'x' } }) // => { tags: ['a', 'b'], 'meta.owner': 'x' } ``` --- ### `get` Gets a value from an object using a dot/bracket-notated path or explicit key array. **Two path forms are supported:** 1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`) are both accepted and mixed freely. - Dot segments are always string keys: `'a.1'` → key `'1'` (string). - Bracket segments are always number keys: `'a[1]'` → key `1` (number). - String literal paths give **full compile-time type inference** on the return type. - Dynamic (non-literal) strings return `unknown`. 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys, no parsing performed. Enables symbol-keyed traversal and compile-time type inference: `get(obj, ['a', 'b'] as const)` infers the return type from the path. Both forms support **all objects** (plain objects, arrays, class instances). Symbol keys are only reachable via the key-array form. ```typescript import { get } from '@helpers4/object'; get(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D ``` **Parameters:** - `obj: null | undefined` — The object to read from - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]` - `defaultValue: D` — Returned when the path is absent or resolves to `undefined` **Returns:** `D` — The value at the path, or `defaultValue` ```typescript import { get } from '@helpers4/object'; get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined ``` **Parameters:** - `obj: null | undefined` — The object to read from - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]` **Returns:** `undefined` — The value at the path, or `defaultValue` ```typescript import { get } from '@helpers4/object'; get(obj: T | null | undefined, path: P, defaultValue?: DeepGet>): DeepGet> | undefined ``` **Parameters:** - `obj: T | null | undefined` — The object to read from - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]` - `defaultValue?: DeepGet>` — Returned when the path is absent or resolves to `undefined` **Returns:** `DeepGet> | undefined` — The value at the path, or `defaultValue` **Examples:** *Access a nested property* Uses a dot-notated path to retrieve a deeply nested value. ```typescript get({ a: { b: { c: 42 } } }, 'a.b.c') // => 42 ``` *Return default for missing path* Returns the default value when the path does not exist. ```typescript get({ a: 1 }, 'b.c', 'default') // => 'default' ``` *Get via key array (supports symbols)* Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys. ```typescript const id = Symbol('id') get({ [id]: 'alice' }, [id]) // => 'alice' ``` --- ### `groupBy` Groups an array of items by a key derived from each item. A thin, typed wrapper around `Object.groupBy` (ES2024) that works on older targets and provides stricter return-type inference. `null` and `undefined` are treated as empty arrays and return `{}`. Items whose computed key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { groupBy } from '@helpers4/object'; groupBy(items: readonly T[] | null | undefined, keyFn: function): Partial> ``` **Parameters:** - `items: readonly T[] | null | undefined` — The array to group - `keyFn: function` — Function that returns the group key for each item **Returns:** `Partial>` — A record mapping each key to the array of items with that key **Examples:** *Group numbers by parity* Groups elements by the string key returned by the callback. ```typescript groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd') // => { odd: [1, 3], even: [2, 4] } ``` *Group objects by a property* Use a property accessor as the grouping key. ```typescript const users = [ { name: 'Alice', role: 'admin' }, { name: 'Bob', role: 'user' }, { name: 'Carol', role: 'admin' }, ]; groupBy(users, u => u.role) // => { admin: [{...Alice}, {...Carol}], user: [{...Bob}] } ``` --- ### `invert` Returns a new object with keys and values swapped. If multiple keys share the same value, the last one wins. `null` and `undefined` are treated as empty objects and return `{}`. Entries whose source key **or** value is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { invert } from '@helpers4/object'; invert(obj: Record | null | undefined): Record ``` **Parameters:** - `obj: Record | null | undefined` — The object whose keys and values are to be swapped **Returns:** `Record` — A new object with values as keys and original keys as values **Examples:** *Swap keys and values* Returns a new object with keys and values swapped. ```typescript invert({ a: 'x', b: 'y', c: 'z' }) // => { x: 'a', y: 'b', z: 'c' } ``` *Build a reverse lookup map* Useful when you have a code-to-label map and need label-to-code. ```typescript const STATUS_LABELS = { 200: 'OK', 404: 'Not Found', 500: 'Internal Server Error' }; const LABEL_TO_CODE = invert(STATUS_LABELS); LABEL_TO_CODE['OK']; // => '200' ``` --- ### `isEmpty` Checks if a plain object has no own enumerable string-keyed properties. `null` and `undefined` are treated as empty objects and return `true`. Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols` separately if symbol keys matter for your use case. ```typescript import { isEmpty } from '@helpers4/object'; isEmpty(value: Record | null | undefined): boolean ``` **Parameters:** - `value: Record | null | undefined` — The object to check **Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined` **Examples:** *Check if an object has no own string-keyed properties* Returns true for `{}`. Symbol-keyed properties are not counted. ```typescript isEmpty({}) // => true isEmpty({ a: 1 }) // => false isEmpty({ a: undefined }) // => false (key exists even if value is undefined) ``` *Symbol keys are not counted* An object with only symbol-keyed properties is considered empty. ```typescript const sym = Symbol('x'); const obj = { [sym]: 1 }; isEmpty(obj) // => true (only string keys are counted) ``` --- ### `isNonEmpty` Checks if a plain object has at least one own enumerable string-keyed property. `null` and `undefined` are treated as empty objects and return `false`. Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols` separately if symbol keys matter for your use case. ```typescript import { isNonEmpty } from '@helpers4/object'; isNonEmpty(value: Record | null | undefined): value is Record ``` **Parameters:** - `value: Record | null | undefined` — The object to check **Returns:** `value is Record` — `true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`. Acts as a type guard: the `if` branch narrows `Record | null | undefined` to `Record`. **Examples:** *Check if an object has own string-keyed properties* Returns true when at least one own enumerable string key is present. ```typescript isNonEmpty({ a: 1 }) // => true isNonEmpty({ a: undefined }) // => true (key exists) isNonEmpty({}) // => false ``` *Guard before iterating object keys* Use isNonEmpty before looping to avoid processing empty objects. ```typescript function processConfig(config: Record): void { if (!isNonEmpty(config)) { console.warn('Config is empty'); return; } for (const key of Object.keys(config)) { // process each key } } ``` --- ### `map` Transforms the values and/or keys of a plain object in a single pass. Both callbacks are optional and default to identity (no transformation). When `mapValue` is omitted the original values are preserved; when `mapKey` is omitted the original keys are preserved. Note: if two different keys map to the same output key the last one wins (insertion order). Entries whose mapped key is a prototype-polluting string (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { map } from '@helpers4/object'; map, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: function, mapKey?: function): Record ``` **Parameters:** - `obj: TObj | null | undefined` — The source object - `mapValue?: function` — Callback called with `(value, key)` for each entry. Defaults to identity. - `mapKey?: function` — Callback called with `(key, value)` for each entry. Defaults to identity. **Returns:** `Record` — 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 } ``` --- ### `mergeDeep` Merges two or more objects deeply, returning a **new** object without mutating any input. Sources are applied left to right. For each key: - If both sides are plain objects → merged recursively into a new object. - Otherwise → **later source wins** (the earlier value is overwritten). - `undefined` in a source **never overwrites** an existing value. - `null` in a source **does overwrite** and prevents a later source from deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })` → `{ a: { y: 2 } }` — `x: 1` is permanently lost. - Arrays, class instances, and all non-plain-object values are **replaced**, not merged. Own enumerable string and symbol keys of each source are processed at the top level; inherited and non-enumerable properties are skipped. Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are silently ignored. **Note:** symbol keys inside nested plain-object values are not preserved — they are lost when those values are deep-cloned. Only top-level symbol keys survive. **TypeScript return type — intersection semantics** The return type is `A & B & C …` (intersection of all source types). This is accurate when keys are disjoint or share the same type: ```ts mergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓ mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓ ``` When the same key carries **incompatible types** across sources, the intersection resolves to `never` for that key — TypeScript surfaces the conflict rather than silently picking a type: ```ts mergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected ``` At runtime the later source always wins (`'x'`), but the `never` type signals that the caller should align their source types. If intentional, cast the result. ```typescript import { mergeDeep } from '@helpers4/object'; mergeDeep(sources: [rest]): MergeResult ``` **Parameters:** - `sources: [rest]` — Two or more objects to merge (none are mutated) **Returns:** `MergeResult` — A new object that is the deep merge of all sources **Examples:** *Merge two objects deeply* Returns a new object — neither input is mutated. ```typescript mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 }) // => { a: 1, b: { c: 2, d: 3 }, e: 4 } ``` --- ### `omit` Creates a new object without the specified keys. ```typescript import { omit } from '@helpers4/object'; omit, K extends string | number | symbol>(obj: T, keys: readonly K[]): Omit ``` **Parameters:** - `obj: T` — The source object - `keys: readonly K[]` — The keys to omit **Returns:** `Omit` — A new object without the omitted keys ```typescript import { omit } from '@helpers4/object'; omit(obj: undefined, keys: readonly string[]): undefined ``` **Parameters:** - `obj: undefined` — The source object - `keys: readonly string[]` — The keys to omit **Returns:** `undefined` — A new object without the omitted keys ```typescript import { omit } from '@helpers4/object'; omit(obj: null, keys: readonly string[]): null ``` **Parameters:** - `obj: null` — The source object - `keys: readonly string[]` — The keys to omit **Returns:** `null` — A new object without the omitted keys **Examples:** *Omit specific keys* Creates a new object without the specified keys. ```typescript omit({ a: 1, b: 2, c: 3 }, ['b']) // => { a: 1, c: 3 } ``` *Remove sensitive fields* Useful to strip sensitive data before sending to client. ```typescript const user = { id: 1, name: 'Alice', password: 'secret', token: 'abc123' }; omit(user, ['password', 'token']) // => { id: 1, name: 'Alice' } ``` --- ### `omitBy` Creates a new object without the own enumerable entries for which `predicate` returns `true`. Complements omit for when the keys to remove aren't known ahead of time — `omit` takes an explicit key list, `omitBy` takes a predicate. ```typescript import { omitBy } from '@helpers4/object'; omitBy>(obj: T | null | undefined, predicate: function): Partial | null | undefined ``` **Parameters:** - `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged. - `predicate: function` — Called with `(value, key)` for each own enumerable entry **Returns:** `Partial | null | undefined` — A new object without the matching entries **Examples:** *Drop entries matching a predicate* Unlike omit(), the keys to drop don’t need to be known ahead of time. ```typescript omitBy({ a: 1, b: undefined, c: 2 }, (value) => value === undefined) // => { a: 1, c: 2 } ``` *Drop entries by key name* The predicate also receives the key, so filtering by name works too. ```typescript omitBy({ id: 1, name: 'x', _internal: true }, (_v, key) => key.startsWith('_')) // => { id: 1, name: 'x' } ``` --- ### `parsePropertyPath` Parses a dot/bracket-notation property path into an array of string/number key segments — the same notation accepted by get and set. - Dot separators (`.`) split segments; each segment becomes a string key. - Bracket indices (`[n]`) become number keys. - A leading `.` is treated as "current level" and stripped before parsing, so `.[0]` ≡ `[0]` and `.a.b` ≡ `a.b`. - Empty string (or a bare `.`) returns `['']` (addresses the `''` key on the root object). - Consecutive dots (`a..b`) produce an empty-string segment: `['a', '', 'b']`. Results are cached (up to 500 distinct path strings, oldest evicted first) since real-world callers tend to reuse a small, fixed set of literal paths. ```typescript import { parsePropertyPath } from '@helpers4/object'; parsePropertyPath(path: string): readonly string | number[] ``` **Parameters:** - `path: string` — The dot/bracket-notation path string to parse **Returns:** `readonly string | number[]` — The parsed key segments **Examples:** *Parse a dot/bracket-notation path into key segments* The same notation accepted by get() and set() — bracket indices become numbers. ```typescript parsePropertyPath('layers[1].name') // => ['layers', 1, 'name'] ``` *Malformed paths throw instead of silently misparsing* Text trailing a closing bracket within a segment is ambiguous — use 'a[0].b' instead. ```typescript parsePropertyPath('a[0]b') // => throws RangeError ``` --- ### `pick` Creates a new object with only the specified keys. Keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are silently skipped. ```typescript import { pick } from '@helpers4/object'; pick, K extends string | number | symbol>(obj: T, keys: readonly K[]): Pick ``` **Parameters:** - `obj: T` — The source object - `keys: readonly K[]` — The keys to pick **Returns:** `Pick` — A new object with only the picked keys ```typescript import { pick } from '@helpers4/object'; pick(obj: undefined, keys: readonly string[]): undefined ``` **Parameters:** - `obj: undefined` — The source object - `keys: readonly string[]` — The keys to pick **Returns:** `undefined` — A new object with only the picked keys ```typescript import { pick } from '@helpers4/object'; pick(obj: null, keys: readonly string[]): null ``` **Parameters:** - `obj: null` — The source object - `keys: readonly string[]` — The keys to pick **Returns:** `null` — A new object with only the picked keys **Examples:** *Pick specific keys* Creates a new object with only the specified keys. ```typescript pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) // => { a: 1, c: 3 } ``` *Extract user fields* Useful to select only the fields you need from an object. ```typescript const user = { id: 1, name: 'Alice', email: 'alice@example.com', password: 'secret' }; pick(user, ['id', 'name', 'email']) // => { id: 1, name: 'Alice', email: 'alice@example.com' } ``` --- ### `pickBy` Creates a new object with only the own enumerable entries for which `predicate` returns `true`. Complements pick for when the keys to keep aren't known ahead of time — `pick` takes an explicit key list, `pickBy` takes a predicate. ```typescript import { pickBy } from '@helpers4/object'; pickBy>(obj: T | null | undefined, predicate: function): Partial | null | undefined ``` **Parameters:** - `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged. - `predicate: function` — Called with `(value, key)` for each own enumerable entry **Returns:** `Partial | null | undefined` — A new object with only the matching entries **Examples:** *Keep only entries matching a predicate* Unlike pick(), the keys to keep don’t need to be known ahead of time. ```typescript pickBy({ a: 1, b: 0, c: 2 }, (value) => value > 0) // => { a: 1, c: 2 } ``` *Keep entries by key name* The predicate also receives the key, so filtering by name works too. ```typescript pickBy({ _id: 1, name: 'x', _internal: true }, (_v, key) => !key.startsWith('_')) // => { name: 'x' } ``` --- ### `removeUndefinedNull` Remove null and undefined values from an object. ```typescript import { removeUndefinedNull } from '@helpers4/object'; removeUndefinedNull>(obj: T): Partial ``` **Parameters:** - `obj: T` — an object **Returns:** `Partial` — 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(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(input: string, fallback: T): T ``` **Parameters:** - `input: string` — The JSON string to parse. - `fallback: T` — Value returned on failure. Defaults to `null` when omitted. **Returns:** `T` — The parsed value typed as `T`, or `fallback` on failure. **Examples:** *Parse valid JSON* Returns the parsed value when the input is valid JSON. ```typescript safeJsonParse<{ a: number }>('{"a":1}') // => { a: 1 } ``` *Return null on invalid input* Returns null instead of throwing when JSON is malformed. ```typescript safeJsonParse('invalid') // => null ``` *Use a fallback value* Returns the provided fallback when parsing fails. ```typescript safeJsonParse('invalid', []) // => [] ``` --- ### `set` Sets a value in an object at the given path, creating intermediate objects as needed. **Two path forms are supported:** 1. **String path** — dot notation and bracket notation, mixed freely. - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`. - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`. - String literal paths give **full compile-time type inference** on the return type. - Dynamic (non-literal) strings return `T` (same object type). 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys, no parsing performed. Enables symbol-keyed access and precise return-type inference. Both forms support **all objects** (plain objects, arrays, class instances). Symbol keys are only reachable via the key-array form. Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`. Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype` is rejected and the original object is returned unchanged (prototype-pollution guard). ```typescript import { set } from '@helpers4/object'; set(obj: T, path: P, value: V): conditional ``` **Parameters:** - `obj: T` — The object to mutate - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]` - `value: V` — Value to assign at the path **Returns:** `conditional` — The mutated object (same reference, narrowed type) **Examples:** *Set a nested property (dot notation)* Creates intermediate objects as needed. All segments are string keys — including numeric-looking ones like "1". ```typescript set({}, 'a.b.c', 42) // => { a: { b: { c: 42 } } } set({}, 'layers.1.name', 'bg') // => { layers: { '1': { name: 'bg' } } } // '1' is a string key ``` *Set via bracket notation* Square-bracket indices become numeric keys. Useful when the path targets an array element. ```typescript const obj = { layers: [{}, { name: 'old' }] } set(obj, 'layers[1].name', 'new') // => { layers: [{}, { name: 'new' }] } ``` *Set via key array (supports symbols)* Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys. ```typescript const id = Symbol('id') set({}, ['user', id], 'alice') // => { user: { [id]: 'alice' } } ``` --- ### `unflatten` Rebuilds a nested object from a single-level object whose keys are dot-notation paths. The inverse of flatten. Uses set internally, so intermediate nodes are always created as plain objects (never arrays — see flatten's doc for why), and any key segment equal to `__proto__`, `constructor`, or `prototype` is silently rejected (same prototype-pollution guard as `set`). ```typescript import { unflatten } from '@helpers4/object'; unflatten(obj: Record): Record ``` **Parameters:** - `obj: Record` — A single-level object with dot-notation keys **Returns:** `Record` — The rebuilt nested object **Examples:** *Rebuild a nested object from dotted keys* The inverse of flatten() — useful for parsing flat config sources (env vars, .ini files). ```typescript unflatten({ 'server.host': 'localhost', 'server.port': 8080 }) // => { server: { host: 'localhost', port: 8080 } } ``` *Multiple keys under the same parent are merged* Every dotted key contributes to the same nested structure. ```typescript unflatten({ 'a.x': 1, 'a.y': 2 }) // => { a: { x: 1, y: 2 } } ``` --- ### `unset` Removes the value at a dot/bracket-notation path or explicit key array, mutating the object in place. Uses the same path syntax as get/set. A missing intermediate segment is a no-op (nothing to remove), not an error. As with `set`, any path containing a string segment equal to `__proto__`, `constructor`, or `prototype` is rejected and the object is returned unchanged. The removed key stops appearing in `Object.keys`/`for...in` — unlike setting it to `undefined`, which would keep the key present. ```typescript import { unset } from '@helpers4/object'; unset(obj: T, path: string | readonly PropertyKey[]): T ``` **Parameters:** - `obj: T` — The object to mutate - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string, or explicit `PropertyKey[]` **Returns:** `T` — The same object reference, with the key removed if it existed **Examples:** *Remove a nested value by path* Uses the same dot/bracket path syntax as get() and set(). ```typescript const config = { server: { host: 'localhost', debug: true } }; unset(config, 'server.debug') // => { server: { host: 'localhost' } } ``` *Missing paths are a safe no-op* Unlike set(), unset() never creates intermediate objects — there is nothing to remove. ```typescript unset({ a: 1 }, 'x.y.z') // => { a: 1 } (unchanged) ``` --- ### `update` Updates the value at a path by applying a function to its current value, creating intermediate objects as needed. Equivalent to `set(obj, path, updater(get(obj, path)))` in a single call. Uses the same path syntax and type-inference rules as get and set — see those for the full behavior (string vs. `PropertyKey[]` paths, prototype-pollution guarding, etc.). ```typescript import { update } from '@helpers4/object'; update(obj: T, path: P, updater: function): conditional ``` **Parameters:** - `obj: T` — The object to mutate - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]` - `updater: function` — Called with the current value (`undefined` if the path is absent); its return value is written back at the path **Returns:** `conditional` — The mutated object (same reference, narrowed type) **Examples:** *Increment a counter in one call* Equivalent to set(obj, path, updater(get(obj, path))), without repeating the path. ```typescript const state = { count: 1 }; update(state, 'count', (n) => (n ?? 0) + 1) // => { count: 2 } ``` *Missing paths create intermediate objects, like set()* The updater receives undefined when the path does not exist yet. ```typescript const stats: Record = {}; update(stats, 'hits.total', (n: number | undefined) => (n ?? 0) + 1) // => { hits: { total: 1 } } ``` --- ## observable Package: `@helpers4/observable` ### `combine` Combine two observables with a map function and an optional pre-treatment. Note: you can use the pre-treatment to add a filter, a distinctUntilChanged, any other operator that can be used in a pipe, or even an `UntilDestroy` operator. ```typescript import { combine } from '@helpers4/observable'; combine(source1: Observable, source2: Observable, map: function, options?: combineOptions): Observable ``` **Parameters:** - `source1: Observable` — first source of data - `source2: Observable` — second source of data - `map: function` — way to combine data - `options?: combineOptions` — options for the combineLatest operator **Returns:** `Observable` — 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(sources: readonly [ObservableInputTuple]): Observable ``` **Parameters:** - `sources: readonly [ObservableInputTuple]` ```typescript import { combineLatest } from '@helpers4/observable'; combineLatest>>(sourcesObject: T): Observable ``` **Parameters:** - `sourcesObject: T` **Examples:** *Combine array of observables* Combines an array of observables into one that emits arrays of their latest values. ```typescript combineLatest([of(1), of(2), of(3)]) // emits [1, 2, 3] ``` *Handle empty array* Returns an observable that emits an empty array when given no sources. ```typescript combineLatest([]) // emits [] ``` --- ### `isObservable` Checks if a value is an RxJS Observable or any compatible observable. Uses duck-typing: returns `true` for any object with both `.subscribe()` and `.pipe()` methods, covering `Observable`, `Subject`, `BehaviorSubject`, `ReplaySubject`, and any RxJS-compatible observable implementation. ```typescript import { isObservable } from '@helpers4/observable'; isObservable(value: unknown): value is Observable ``` **Parameters:** - `value: unknown` — The value to check **Returns:** `value is Observable` — `true` if value is observable-like **Examples:** *Detect an RxJS Observable or Subject* Returns true for Observable, Subject, BehaviorSubject, and any duck-typed observable. ```typescript import { Observable, Subject } from 'rxjs'; isObservable(new Observable()) // => true isObservable(new Subject()) // => true isObservable(Promise.resolve()) // => false isObservable({}) // => false ``` *Accept either an Observable or a plain value* Use as a guard to normalize inputs that may be Observables or raw values. ```typescript import { Observable, of } from 'rxjs'; function toObservable(value: T | Observable): Observable { return isObservable(value) ? value : of(value); } ``` --- ## promise Package: `@helpers4/promise` ### `consoleLogPromise` Returns a function that logs data to the console and passes it through. ```typescript import { consoleLogPromise } from '@helpers4/promise'; consoleLogPromise(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(fn: function): Promise ``` **Parameters:** - `fn: function` — An async function that receives a `defer` registration function. **Returns:** `Promise` — 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(ms: number, value?: T): Promise ``` **Parameters:** - `ms: number` — Milliseconds to delay - `value?: T` — Optional value to resolve with **Returns:** `Promise` — 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(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(fn: function, defaultValue: T): Promise ``` **Parameters:** - `fn: function` — The function to guard - `defaultValue: T` — The value to return if the function throws **Returns:** `Promise` — The result of the function, or the default value on error ```typescript import { guard } from '@helpers4/promise'; guard(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(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(functions: readonly function[], limit: number): Promise ``` **Parameters:** - `functions: readonly function[]` — Array of functions that return promises - `limit: number` — Maximum number of concurrent executions **Returns:** `Promise` — 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(keys: readonly K[], mapper: function): Promise> ``` **Parameters:** - `keys: readonly K[]` — The keys to resolve - `mapper: function` — Async function called for each key, returning the associated value **Returns:** `Promise>` — 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(fn: function, maxAttempts: number, delayMs: number): Promise ``` **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` — 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(input: URL | RequestInfo, init?: RequestInit, options: SafeFetchOptions): Promise ``` **Parameters:** - `input: URL | RequestInfo` — URL or `Request` object passed to `fetch` - `init?: RequestInit` — Optional `RequestInit` options passed to `fetch` - `options: SafeFetchOptions` (default: `{}`) — Parsing options (default: `{ parse: 'json' }`) **Returns:** `Promise` — 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( 'https://example.com/data.txt', undefined, { parse: 'text' } ); ``` --- ### `settle` Runs an array of promises concurrently and partitions the outcomes instead of rejecting on the first failure, unlike `Promise.all`. Built on top of `Promise.allSettled`, but returns fulfilled values and rejection reasons already split apart so callers don't need to inspect `status` themselves. ```typescript import { settle } from '@helpers4/promise'; settle(promises: readonly Promise[]): Promise ``` **Parameters:** - `promises: readonly Promise[]` — Promises to run concurrently **Returns:** `Promise` — An object with `fulfilled` values and `rejected` reasons, each in input order **Examples:** *Partition fulfilled and rejected outcomes* Runs promises concurrently and splits results instead of rejecting on the first failure. ```typescript const { fulfilled, rejected } = await settle([ Promise.resolve(1), Promise.reject(new Error('boom')), Promise.resolve(3), ]) // => { fulfilled: [1, 3], rejected: [Error('boom')] } ``` *All promises succeed* Returns an empty rejected array when nothing fails. ```typescript const { fulfilled, rejected } = await settle([Promise.resolve('a'), Promise.resolve('b')]) // => { fulfilled: ['a', 'b'], rejected: [] } ``` --- ### `timeout` Wraps a promise to reject with a `TimeoutError` if it does not resolve within the specified duration. ```typescript import { timeout } from '@helpers4/promise'; timeout(promise: Promise, ms: number): Promise ``` **Parameters:** - `promise: Promise` — The promise to wrap - `ms: number` — Timeout duration in milliseconds **Returns:** `Promise` — 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(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(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' ``` --- ### `dedent` Strips the common leading whitespace from every line of a multi-line string, and trims a single leading/trailing blank line if present. Lets you write readable, indented multi-line strings in source code (typically template literals) without that indentation leaking into the output. ```typescript import { dedent } from '@helpers4/string'; dedent(str: string): string ``` **Parameters:** - `str: string` — The string to dedent **Returns:** `string` — The dedented string **Examples:** *Write readable multi-line strings without leaking source indentation* Strips the common leading whitespace and the wrapping blank lines. ```typescript dedent(` Hello World `) // => 'Hello\n World' ``` *The minimum indentation across all lines is what gets removed* Lines with more indentation than the minimum keep their relative indent. ```typescript dedent(' a\n b\n c') // => ' a\nb\n c' ``` --- ### `escapeHtml` Escapes the HTML special characters `&`, `<`, `>`, `"`, and `'` in a string. Use this to safely embed untrusted content into HTML attribute values or text nodes without risk of XSS injection. ```typescript import { escapeHtml } from '@helpers4/string'; escapeHtml(str: string): string ``` **Parameters:** - `str: string` — The string to escape. **Returns:** `string` — The escaped string. **Examples:** *Escape script tags* Converts < > " ' & to their HTML entities to prevent XSS. ```typescript escapeHtml('') // => '<script>alert("xss")</script>' ``` *Safe interpolation in templates* Safe for inserting untrusted strings into HTML. ```typescript const userInput = 'bold'; escapeHtml(userInput) // => '<b>bold</b>' ``` --- ### `escapeRegExp` Escapes regular expression metacharacters (`. * + ? ^ $ { } ( ) | [ ] \`) in a string so it can be safely embedded in a `RegExp` pattern. Use this before building a `RegExp` from untrusted or dynamic input — without it, characters like `.` or `(` change the pattern's meaning instead of being matched literally. ```typescript import { escapeRegExp } from '@helpers4/string'; escapeRegExp(str: string): string ``` **Parameters:** - `str: string` — The string to escape **Returns:** `string` — The escaped string, safe to embed in a `RegExp` pattern **Examples:** *Escape metacharacters before building a RegExp* Without escaping, "." and "?" would change the pattern's meaning. ```typescript escapeRegExp('1 + 1 = 2?') // => '1 \\+ 1 = 2\\?' ``` *Safely search for untrusted user input* Escaping lets user-provided text be matched literally instead of as a pattern. ```typescript const userInput = 'a.b'; new RegExp(escapeRegExp(userInput)).test('a.b') // => true new RegExp(escapeRegExp(userInput)).test('axb') // => false (literal '.', not "any char") ``` --- ### `extractErrorMessage` Convert an error to a readable message. ```typescript import { extractErrorMessage } from '@helpers4/string'; extractErrorMessage(error: unknown, stringify: string | true): string ``` **Parameters:** - `error: unknown` — an error - `stringify: string | true` — stringifies the error if no extractable message is found **Returns:** `string` — a readable message or a stringified error if stringify is true, otherwise undefined ```typescript import { extractErrorMessage } from '@helpers4/string'; extractErrorMessage(error?: unknown, stringify?: string | boolean): string | undefined ``` **Parameters:** - `error?: unknown` — an error - `stringify?: string | boolean` — stringifies the error if no extractable message is found **Returns:** `string | undefined` — a readable message or a stringified error if stringify is true, otherwise undefined **Examples:** *Extract message from Error object* Returns the stringified Error, including the class prefix. ```typescript extractErrorMessage(new Error('Something went wrong')) // => 'Error: Something went wrong' ``` *Handle string errors* Returns the string directly when the error is a plain string. ```typescript extractErrorMessage('plain error') // => 'plain error' ``` *Stringify unknown errors* When stringify is true, falls back to JSON.stringify for unrecognized errors. ```typescript extractErrorMessage(42, true) // => '42' ``` --- ### `formatProgressBar` Formats a value as a text progress bar, repeating `filledChar`/`emptyChar` across `width` cells proportional to `value / max`. `value` is clamped to `[0, max]` before computing the ratio — out-of-range values (negative, above `max`) produce an empty or fully-filled bar instead of throwing. Non-finite `max` (`NaN`, `Infinity`) is treated as `0`, yielding an empty bar. ```typescript import { formatProgressBar } from '@helpers4/string'; formatProgressBar(value: number, options: ProgressBarOptions): string ``` **Parameters:** - `value: number` — The current value - `options: ProgressBarOptions` (default: `{}`) — Bar rendering options **Returns:** `string` — A string of `width` repeated filled/empty characters **Examples:** *Default 20-cell bar* Renders a percentage as a filled/empty block bar using the default width and characters. ```typescript formatProgressBar(65) // => '▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░' ``` *Custom width, characters, and scale* Renders a value against a custom max, width, and fill characters — useful for anything scored out of a different total than 100. ```typescript formatProgressBar(3, { width: 10, max: 5, filledChar: '#', emptyChar: '-' }) // => '######----' ``` --- ### `injectWordBreaks` Adds word-break opportunities to a string so it can wrap cleanly in narrow UI containers such as side panels or table cells. Invisible zero-width spaces (`\u200B`) are inserted at meaningful boundaries — camelCase splits, path separators, token edges — while protected spans (URLs, emails, HTML) and atomic numeric values (`-0.1%`, `12ms`, `1e-3`) are never broken. The visible text content is unchanged. ```typescript import { injectWordBreaks } from '@helpers4/string'; injectWordBreaks(str: string): string ``` **Parameters:** - `str: string` — The string to process. **Returns:** `string` — The string with word-break opportunities injected at meaningful boundaries. ```typescript import { injectWordBreaks } from '@helpers4/string'; injectWordBreaks(str: undefined): undefined ``` **Parameters:** - `str: undefined` — The string to process. **Returns:** `undefined` — The string with word-break opportunities injected at meaningful boundaries. ```typescript import { injectWordBreaks } from '@helpers4/string'; injectWordBreaks(str: null): null ``` **Parameters:** - `str: null` — The string to process. **Returns:** `null` — The string with word-break opportunities injected at meaningful boundaries. **Examples:** *camelCase identifier* Inserts ZWS at each camelCase boundary so a long identifier can wrap in a narrow column. ```typescript injectWordBreaks('getUserProfileData') // => 'get\u200BUser\u200BProfile\u200BData' ``` *Comma-separated tokens* The comma attaches to the left token so a line never starts with a comma. ```typescript injectWordBreaks('foo,bar') // => 'foo,\u200Bbar' ``` *Atomic numeric value* Signed decimals and numbers with units (-0.1%, 12ms, -2.4E+6) are never split. ```typescript injectWordBreaks('-0.1%') // => '-0.1%' (unchanged — atomic value) ``` *File path* Slashes become wrap points so a long path can break at each component. ```typescript injectWordBreaks('path/to/my_file') // => 'path\u200B/\u200Bto\u200B/\u200Bmy_file' ``` *URL is preserved intact* URLs are D0-protected spans — no ZWS is inserted inside or adjacent to them. ```typescript injectWordBreaks('https://example.com/foo/bar') // => 'https://example.com/foo/bar' (unchanged) ``` --- ### `isBlank` Checks if a string is blank — empty or contains only whitespace characters. `null` and `undefined` are considered blank and return `true`. Uses `String.prototype.trim()` internally, which covers all ECMAScript whitespace: standard ASCII whitespace (`\t`, `\n`, `\r`, `\f`, `\v`), non-breaking space (U+00A0), BOM (U+FEFF), and all Unicode "Space_Separator" category characters (en space, em space, thin space, ideographic space, etc.). **Zero-width characters** (U+200B zero-width space, U+200C, U+200D, U+2060) are **not** treated as whitespace — they are Unicode "Format" (Cf) characters, not spaces. Strip them explicitly if needed: `isBlank(value.replace(/[​-‍⁠]/g, ''))` ```typescript import { isBlank } from '@helpers4/string'; isBlank(value: string | null | undefined): boolean ``` **Parameters:** - `value: string | null | undefined` — The string to check **Returns:** `boolean` — `true` if the string is empty, contains only whitespace, or is `null`/`undefined` **Examples:** *Detect empty or whitespace-only strings* Returns true for "" and for any string made entirely of whitespace — including non-breaking space (U+00A0), en/em spaces, ideographic space, and BOM. ```typescript isBlank('') // => true isBlank(' ') // => true isBlank('\t\n') // => true isBlank(' ') // => true (non-breaking space U+00A0) isBlank('foo') // => false isBlank(' x ') // => false ``` *Form validation — reject blank input* Use isBlank to reject fields that contain only whitespace. ```typescript function validateName(name: string): string | null { if (isBlank(name)) return 'Name is required'; return null; } validateName('') // => 'Name is required' validateName(' ') // => 'Name is required' validateName('Ada') // => null ``` --- ### `isEmpty` Checks if a string is empty (`""`), `null`, or `undefined`. This is a strict emptiness check — whitespace-only strings are **not** considered empty. Use `isEmpty(value?.trim())` if you need to treat blank strings as empty. ```typescript import { isEmpty } from '@helpers4/string'; isEmpty(value: string | null | undefined): value is "" | null | undefined ``` **Parameters:** - `value: string | null | undefined` — The string to check **Returns:** `value is "" | null | undefined` — `true` if the string is `""`, `null`, or `undefined` **Examples:** *Check if a string is empty* Returns true only for `""`. Whitespace-only strings are not considered empty. ```typescript isEmpty('') // => true isEmpty(' ') // => false (whitespace is content) isEmpty('foo') // => false ``` *Treat blank strings as empty by trimming first* Compose with .trim() when whitespace-only should also be considered empty. ```typescript isEmpty(''.trim()) // => true isEmpty(' '.trim()) // => true isEmpty('hi'.trim()) // => false ``` --- ### `isNonEmpty` Checks if a string is non-empty (has at least one character). `null` and `undefined` are considered empty and return `false`. Whitespace-only strings are considered non-empty. Use `isNonEmpty(value?.trim())` if you need to exclude blank strings. ```typescript import { isNonEmpty } from '@helpers4/string'; isNonEmpty(value: string | null | undefined): value is string ``` **Parameters:** - `value: string | null | undefined` — The string to check **Returns:** `value is string` — `true` if the string has at least one character; `false` for `""`, `null`, or `undefined`. Acts as a type guard: the `if` branch narrows `string | null | undefined` to `string`. **Examples:** *Check if a string has content* Returns true for any string with at least one character, including whitespace. ```typescript isNonEmpty('hello') // => true isNonEmpty(' ') // => true (whitespace is content) isNonEmpty('') // => false ``` *Exclude blank strings by trimming first* Compose with .trim() when whitespace-only strings should be treated as empty. ```typescript isNonEmpty('hello'.trim()) // => true isNonEmpty(' '.trim()) // => false isNonEmpty(''.trim()) // => false ``` --- ### `isNotBlank` Checks if a string is not blank — non-empty and contains at least one non-whitespace character. `null` and `undefined` are considered blank and return `false`. Uses `String.prototype.trim()` internally. See `isBlank` for the full list of characters considered whitespace (includes non-breaking space, en/em space, ideographic space, etc.). ```typescript import { isNotBlank } from '@helpers4/string'; isNotBlank(value: string | null | undefined): boolean ``` **Parameters:** - `value: string | null | undefined` — The string to check **Returns:** `boolean` — `true` if the string has at least one non-whitespace character; `false` for blank, `null`, or `undefined` **Examples:** *Check that a string has real content* Returns true only when the string contains at least one non-whitespace character. ```typescript isNotBlank('foo') // => true isNotBlank(' x ') // => true isNotBlank('') // => false isNotBlank(' ') // => false isNotBlank('\t') // => false ``` *Filter out blank strings from an array* Use as a predicate in .filter() to keep only strings with real content. ```typescript const tags = ['typescript', ' ', '', 'helpers']; tags.filter(isNotBlank) // => ['typescript', 'helpers'] ``` --- ### `kebabCase` Converts 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. `null` and `undefined` are passed through unchanged. A sentence boundary is detected at the first occurrence of `.`, `?`, `!`, `…`, or `;` followed by whitespace or end of string. Newlines are collapsed to spaces before matching. If no boundary is found the entire (cleaned) string is returned. To cap the result at a maximum length, combine with truncate: ```ts truncate(leadingSentence(input), 120) ``` ```typescript import { leadingSentence } from '@helpers4/string'; leadingSentence(input: string): string ``` **Parameters:** - `input: string` — The source string **Returns:** `string` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish ```typescript import { leadingSentence } from '@helpers4/string'; leadingSentence(input: null): null ``` **Parameters:** - `input: null` — The source string **Returns:** `null` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish ```typescript import { leadingSentence } from '@helpers4/string'; leadingSentence(input: undefined): undefined ``` **Parameters:** - `input: undefined` — The source string **Returns:** `undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish ```typescript import { leadingSentence } from '@helpers4/string'; leadingSentence(input: string | null | undefined): string | null | undefined ``` **Parameters:** - `input: string | null | undefined` — The source string **Returns:** `string | null | undefined` — The first sentence, including its terminal character; or `null`/`undefined` if input is nullish **Examples:** *Extract the leading sentence* Returns the first sentence, terminated by . ? or !. ```typescript leadingSentence('Returns the sum of an array. Works with any numbers.') // => 'Returns the sum of an array.' ``` *Works with ? and !* Recognises question marks and exclamation marks as sentence terminators. ```typescript leadingSentence('Is it done? Yes it is!') // => 'Is it done?' ``` *Cap length by combining with truncate* Use truncate to limit the result to a fixed number of characters. ```typescript truncate(leadingSentence(input), 120) ``` --- ### `pascalCase` Converts a string to PascalCase. Handles camelCase, kebab-case, snake_case, spaces, and mixed formats. ```typescript import { pascalCase } from '@helpers4/string'; pascalCase(str: string): string ``` **Parameters:** - `str: string` — The string to convert **Returns:** `string` — String in PascalCase ```typescript import { pascalCase } from '@helpers4/string'; pascalCase(str: undefined): undefined ``` **Parameters:** - `str: undefined` — The string to convert **Returns:** `undefined` — String in PascalCase ```typescript import { pascalCase } from '@helpers4/string'; pascalCase(str: null): null ``` **Parameters:** - `str: null` — The string to convert **Returns:** `null` — String in PascalCase **Examples:** *Convert kebab-case to PascalCase* Converts a kebab-case string to PascalCase. ```typescript pascalCase('my-component') // => 'MyComponent' ``` *Convert snake_case to PascalCase* Also handles snake_case and other formats. ```typescript pascalCase('user_first_name') // => 'UserFirstName' ``` --- ### `removeDiacritics` Removes diacritical marks (accents) from a string, e.g. `'café'` → `'cafe'`. Works by Unicode-decomposing each character into its base letter plus combining marks (`'é'` → `'e'` + a combining acute accent), then stripping the marks. Same technique already used internally by `slugify`. ```typescript import { removeDiacritics } from '@helpers4/string'; removeDiacritics(str: string): string ``` **Parameters:** - `str: string` — The string to strip diacritics from **Returns:** `string` — The string with diacritics removed **Examples:** *Strip accents from a string* Useful for accent-insensitive search or generating ASCII-safe identifiers. ```typescript removeDiacritics('café') // => 'cafe' ``` *Plain ASCII text is left unchanged* Only characters with combining diacritical marks are affected. ```typescript removeDiacritics('hello world') // => 'hello world' ``` --- ### `slugify` Converts a string into a URL-friendly slug. ```typescript import { slugify } from '@helpers4/string'; slugify(str: string): string ``` **Parameters:** - `str: string` — The string to convert into a slug. **Returns:** `string` — A lowercase, hyphen-separated slug safe for URLs. ```typescript import { slugify } from '@helpers4/string'; slugify(str: undefined): undefined ``` **Parameters:** - `str: undefined` — The string to convert into a slug. **Returns:** `undefined` — A lowercase, hyphen-separated slug safe for URLs. ```typescript import { slugify } from '@helpers4/string'; slugify(str: null): null ``` **Parameters:** - `str: null` — The string to convert into a slug. **Returns:** `null` — A lowercase, hyphen-separated slug safe for URLs. **Examples:** *Create a URL-safe slug* Converts a string into a lowercase, hyphen-separated slug. ```typescript slugify('Hello World!') // => 'hello-world' ``` *Handle accented characters* Normalizes Unicode characters and strips diacritics. ```typescript slugify('Crème brûlée') // => 'creme-brulee' ``` --- ### `snakeCase` Converts a string to snake_case. Handles camelCase, PascalCase, kebab-case, spaces, and mixed formats. ```typescript import { snakeCase } from '@helpers4/string'; snakeCase(str: string): string ``` **Parameters:** - `str: string` — The string to convert **Returns:** `string` — String in snake_case ```typescript import { snakeCase } from '@helpers4/string'; snakeCase(str: undefined): undefined ``` **Parameters:** - `str: undefined` — The string to convert **Returns:** `undefined` — String in snake_case ```typescript import { snakeCase } from '@helpers4/string'; snakeCase(str: null): null ``` **Parameters:** - `str: null` — The string to convert **Returns:** `null` — String in snake_case **Examples:** *Convert camelCase to snake_case* Converts a camelCase string to snake_case. ```typescript snakeCase('myVariableName') // => 'my_variable_name' ``` *Convert kebab-case to snake_case* Also handles kebab-case and other formats. ```typescript snakeCase('my-component-name') // => 'my_component_name' ``` --- ### `template` Interpolates `{{key}}` placeholders in a template string with values from a data record. Unknown keys are replaced with an empty string. No `eval` or `Function` constructor is used — substitution is purely regex-based. Nested expressions and logic are intentionally out of scope. ```typescript import { template } from '@helpers4/string'; template(str: string, data: Record): string ``` **Parameters:** - `str: string` — The template string containing `{{key}}` placeholders. - `data: Record` — A record mapping placeholder names to replacement values. **Returns:** `string` — The template string with all placeholders replaced. **Examples:** *Simple interpolation* Replaces {{key}} placeholders with values from the data object. ```typescript template('Hello, {{name}}!', { name: 'Alice' }) // => 'Hello, Alice!' ``` *Multiple placeholders* All matching placeholders are replaced in a single pass. ```typescript template('{{greeting}}, {{name}}!', { greeting: 'Hi', name: 'Bob' }) // => 'Hi, Bob!' ``` *Missing keys become empty string* Unknown placeholders are replaced with an empty string. ```typescript template('Hello, {{name}}!', {}) // => 'Hello, !' ``` --- ### `titleCase` Converts a string to Title Case. Handles camelCase, PascalCase, kebab-case, snake_case, spaces, and mixed formats. ```typescript import { titleCase } from '@helpers4/string'; titleCase(str: string): string ``` **Parameters:** - `str: string` — The string to convert **Returns:** `string` — String in Title Case ```typescript import { titleCase } from '@helpers4/string'; titleCase(str: undefined): undefined ``` **Parameters:** - `str: undefined` — The string to convert **Returns:** `undefined` — String in Title Case ```typescript import { titleCase } from '@helpers4/string'; titleCase(str: null): null ``` **Parameters:** - `str: null` — The string to convert **Returns:** `null` — String in Title Case **Examples:** *Convert kebab-case to Title Case* Transforms a delimited string into Title Case. ```typescript titleCase('my-component-name') // => 'My Component Name' ``` *Convert camelCase to Title Case* Also handles camelCase by splitting on uppercase transitions. ```typescript titleCase('queryItems') // => 'Query Items' ``` --- ### `truncate` Truncates a string to `maxLength` characters, appending an ellipsis when cut. The ellipsis counts toward `maxLength`, so the result is always at most `maxLength` characters long. If the string is already within the limit, it is returned unchanged (no ellipsis appended). `null` and `undefined` inputs are returned as-is to align with other string helpers. ```typescript import { truncate } from '@helpers4/string'; truncate(input: undefined, maxLength: number, ellipsis?: string): undefined ``` **Parameters:** - `input: undefined` — The string to truncate. - `maxLength: number` — Maximum number of characters in the output (including ellipsis). - `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`. **Returns:** `undefined` — The (possibly truncated) string, or the input itself when `null`/`undefined`. ```typescript import { truncate } from '@helpers4/string'; truncate(input: null, maxLength: number, ellipsis?: string): null ``` **Parameters:** - `input: null` — The string to truncate. - `maxLength: number` — Maximum number of characters in the output (including ellipsis). - `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`. **Returns:** `null` — The (possibly truncated) string, or the input itself when `null`/`undefined`. ```typescript import { truncate } from '@helpers4/string'; truncate(input: string, maxLength: number, ellipsis?: string): string ``` **Parameters:** - `input: string` — The string to truncate. - `maxLength: number` — Maximum number of characters in the output (including ellipsis). - `ellipsis?: string` — Appended when the string is cut. Defaults to `'…'`. **Returns:** `string` — The (possibly truncated) string, or the input itself when `null`/`undefined`. **Examples:** *Truncate with default ellipsis* Appends … when the string exceeds the limit. ```typescript truncate('Hello, world!', 8) // => 'Hello, …' ``` *Truncate with custom ellipsis* The ellipsis counts toward the maxLength. ```typescript truncate('Hello, world!', 8, '...') // => 'Hello...' ``` *String within limit* Returned unchanged when already short enough. ```typescript truncate('Hi', 10) // => 'Hi' ``` --- ### `unescapeHtml` Unescapes the HTML entities `&`, `<`, `>`, `"`, and `'` back to `&`, `<`, `>`, `"`, and `'`. This is the exact inverse of escapeHtml — it only recognizes the five entities that function produces, not the full HTML entity set (no ` `, no numeric code points beyond `'`, etc.). ```typescript import { unescapeHtml } from '@helpers4/string'; unescapeHtml(str: string): string ``` **Parameters:** - `str: string` — The string to unescape **Returns:** `string` — The unescaped string **Examples:** *Unescape HTML entities back to their original characters* Inverse of escapeHtml — turns <, >, &, ", ' back into < > & " '. ```typescript unescapeHtml('<b>bold</b>') // => 'bold' ``` *Unrecognized entities are left untouched* Only the five entities escapeHtml produces are unescaped — not the full HTML entity set. ```typescript unescapeHtml('a b') // => 'a b' (unchanged) ``` --- ### `words` Splits a string into an array of words. `null` and `undefined` return `[]`. Handles camelCase, PascalCase, SCREAMING_SNAKE_CASE, kebab-case, snake_case, and regular whitespace-separated text. Numbers are treated as word tokens. ```typescript import { words } from '@helpers4/string'; words(str: string | null | undefined): string[] ``` **Parameters:** - `str: string | null | undefined` — The string to split into words. **Returns:** `string[]` — An array of word tokens. **Examples:** *Split common string formats* Splits camelCase, PascalCase, snake_case, kebab-case and space-separated words. ```typescript words('camelCaseString') // => ['camel', 'Case', 'String'] words('snake_case') // => ['snake', 'case'] words('kebab-case') // => ['kebab', 'case'] words('hello world') // => ['hello', 'world'] ``` *Build camelCase from any input* Combine with a map to convert from any naming convention. ```typescript const toCamel = (str: string) => words(str) .map((w, i) => i === 0 ? w.toLowerCase() : w[0].toUpperCase() + w.slice(1).toLowerCase()) .join(''); toCamel('hello-world'); // => 'helloWorld' ``` --- ## type Package: `@helpers4/type` ### `Brand` Brands a base type `T` with a phantom tag `B` to create a nominal type. Two `Brand` and `Brand` are structurally identical strings at runtime, but TypeScript treats them as distinct types at the call site — preventing accidental mix-ups. Use a const-assertion cast at the creation boundary: ```ts type UserId = Brand; const toUserId = (s: string): UserId => s as UserId; ``` **Examples:** *Brand* ```typescript ```ts type Meter = Brand; type Second = Brand; declare function speed(distance: Meter, time: Second): number; const d = 100 as Meter; const t = 5 as Second; speed(d, t); // ✅ speed(t, d); // ✗ Type error — args swapped ``` ``` --- ### `DeepGet` Resolves the value type at a given `Path` within `T`. Returns `unknown` when any key in `Path` is not present in the corresponding level of `T`. An empty path resolves to `T` itself. A path segment that goes through an optional property keeps the result nullable (`V | undefined`) instead of degrading to `unknown`. **Examples:** *DeepGet* ```typescript ```ts type Obj = { a: { b: { c: number } } }; DeepGet // => number DeepGet // => { c: number } DeepGet // => unknown DeepGet // => Obj type WithOptional = { a?: { b: number } }; DeepGet // => number | undefined ``` ``` --- ### `DeepPartial` Recursively makes all properties of T optional, including nested objects and array elements. **Examples:** *DeepPartial* ```typescript ```ts type Config = { server: { host: string; port: number }; debug: boolean }; type PartialConfig = DeepPartial; // => { server?: { host?: string; port?: number }; debug?: boolean } ``` ``` --- ### `DeepSet` Produces the type of `T` after replacing the value at `Path` with `V`. When a key in `Path` is absent from the corresponding level of `T`, that level (and everything below it) is added as a new field instead of resolving to `never` — mirroring how `set()` creates intermediate objects at runtime. **Examples:** *DeepSet* ```typescript ```ts type Obj = { a: { b: number; c: string } }; DeepSet // => { a: { b: string; c: string } } DeepSet // => { a: { b: number; c: string } & { x: boolean } } ``` ``` --- ### `DeepWritable` Recursively removes `readonly` from all properties of T, including nested objects, array elements, and tuple positions. **Examples:** *DeepWritable* ```typescript ```ts type Config = { readonly server: { readonly host: string }; readonly tags: readonly string[] }; type MutableConfig = DeepWritable; // => { server: { host: string }; tags: string[] } ``` ``` *DeepWritable* ```typescript ```ts type Point = readonly [x: number, y: number]; type MutablePoint = DeepWritable; // => [x: number, y: number] Note: `Date`, `Map`, `Set`, `Promise`, and `RegExp` are treated as opaque and passed through unchanged. In particular, `DeepWritable>` does **not** strip `readonly` from the value type `V` — use a manual mapped type if you need that. ``` ``` --- ### `KeysOfType` Extracts the keys of `T` whose values extend `V`. Optional properties are matched by their non-nullable value type, so an optional `string` property still counts as a `string` key. **Examples:** *KeysOfType* ```typescript ```ts type User = { id: number; name: string; email: string; active: boolean }; type StringKeys = KeysOfType; // 'name' | 'email' type NumberKeys = KeysOfType; // 'id' ``` ``` --- ### `Maybe` Type for values that can be T, undefined, or null. --- ### `Nullable` Adds `null` to a type (`T | null`). Useful as a shorthand when explicit nullability should be expressed in function signatures or generic constraints. **Examples:** *Nullable* ```typescript ```ts type MaybeUser = Nullable; // User | null function findUser(id: string): Nullable { ... } ``` ``` --- ### `Nullish` Adds `null` and `undefined` to a type (`T | null | undefined`). Alias of Maybe. **Examples:** *Nullish* ```typescript ```ts type OptionalUser = Nullish; // User | null | undefined ``` ``` --- ### `OmitByValue` Constructs a type by omitting all entries of `T` whose values extend `V`. Optional properties are matched by their non-nullable value type, so an optional `string` property is omitted the same as a required one. **Examples:** *OmitByValue* ```typescript ```ts type Form = { name: string; age: number; email: string; active: boolean }; type NonStringFields = OmitByValue; // { age: number; active: boolean } ``` ``` --- ### `OptionalKeys` Extracts the optional keys of an object type `T`. **Examples:** *OptionalKeys* ```typescript ```ts type User = { id: number; name: string; email?: string }; type Opts = OptionalKeys; // 'email' ``` ``` --- ### `PickByValue` Constructs a type by picking all entries of `T` whose values extend `V`. Optional properties are matched by their non-nullable value type, so an optional `string` property is picked the same as a required one. **Examples:** *PickByValue* ```typescript ```ts type Form = { name: string; age: number; email: string; active: boolean }; type StringFields = PickByValue; // { name: string; email: string } ``` ``` --- ### `Prettify` Flattens an intersection type into a single readable object type. IDE tooltips for intersections like `A & B & C` often show the raw intersection instead of the resolved shape. Wrapping with `Prettify` forces TypeScript to expand and display the fully-resolved type. Distributes over unions, so each member is prettified independently instead of collapsing to their shared keys. **Examples:** *Prettify* ```typescript ```ts type A = { a: number }; type B = { b: string }; type Merged = A & B; // shown as "A & B" in IDE type Pretty = Prettify; // shown as "{ a: number; b: string }" ``` ``` --- ### `RequiredKeys` Extracts the required (non-optional) keys of an object type `T`. **Examples:** *RequiredKeys* ```typescript ```ts type User = { id: number; name: string; email?: string }; type Required = RequiredKeys; // 'id' | 'name' ``` ``` --- ### `UnionToIntersection` Converts a union type to an intersection type: `A | B | C` → `A & B & C`. Uses conditional-type distribution and the contravariant position of a function parameter to collapse the union into an intersection. **Examples:** *UnionToIntersection* ```typescript ```ts type Union = { a: number } | { b: string } | { c: boolean }; type Intersection = UnionToIntersection; // { a: number } & { b: string } & { c: boolean } type Keys = UnionToIntersection<'a' | 'b'>; // 'a' & 'b' (resolves to never for disjoint literals) ``` ``` --- ### `ValueOf` Produces a union of all value types of an object type `T`. **Examples:** *ValueOf* ```typescript ```ts type Config = { host: string; port: number; secure: boolean }; type ConfigValue = ValueOf; // string | number | boolean const STATUS = { OK: 200, NOT_FOUND: 404, ERROR: 500 } as const; type StatusCode = ValueOf; // 200 | 404 | 500 ``` ``` --- ## url Package: `@helpers4/url` ### `cleanPath` Clean an URL by removing duplicate slashes. The protocol part of the URL is not modified. ```typescript import { cleanPath } from '@helpers4/url'; cleanPath(url: string | null | undefined): string | null | undefined ``` **Parameters:** - `url: string | null | undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string | null | undefined` — The cleaned URL string, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Remove duplicate slashes* Cleans an URL by removing duplicate slashes while preserving the protocol. ```typescript cleanPath('/path//to///resource') // => '/path/to/resource' ``` *Preserve protocol* The double slash after the protocol (http://) is not modified. ```typescript cleanPath('http://example.com//path') // => 'http://example.com/path' ``` *Handle null and undefined* Returns null for null input and undefined for undefined input. ```typescript cleanPath(null) // => null cleanPath(undefined) // => undefined ``` --- ### `extractPureURI` Extracts the pure URI from a URL by removing query parameters and fragments. ```typescript import { extractPureURI } from '@helpers4/url'; extractPureURI(url: string): string ``` **Parameters:** - `url: string` — The URL string to process **Returns:** `string` — The URI without query parameters and fragments, or the original value if undefined/null ```typescript import { extractPureURI } from '@helpers4/url'; extractPureURI(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to process **Returns:** `undefined` — The URI without query parameters and fragments, or the original value if undefined/null ```typescript import { extractPureURI } from '@helpers4/url'; extractPureURI(url: null): null ``` **Parameters:** - `url: null` — The URL string to process **Returns:** `null` — The URI without query parameters and fragments, or the original value if undefined/null **Examples:** *Remove query parameters and fragments* Strips everything after ? or # from the URL. ```typescript extractPureURI('https://example.com/path?query=1#section') // => 'https://example.com/path' ``` --- ### `onlyPath` Extract only the path from an URI with optional query and fragments. For example, all these parameters will return `/path`: - `/path` - `/path?query=thing` - `/path#fragment` - `/path?query=thing#fragment` ```typescript import { onlyPath } from '@helpers4/url'; onlyPath(url: string): string ``` **Parameters:** - `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { onlyPath } from '@helpers4/url'; onlyPath(url: null): null ``` **Parameters:** - `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `null` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { onlyPath } from '@helpers4/url'; onlyPath(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `undefined` — The URL string without query and fragment, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Extract the path from a URL* Strips query parameters and fragments from a URL path. ```typescript onlyPath('/path?query=thing#fragment') // => '/path' ``` --- ### `parsePackageRepository` Parse the `repository` field from `package.json` into a structured object. Supports all npm-specified formats: - **Object form**: `{ "type": "git", "url": "...", "directory": "..." }` - **GitHub shorthand**: `"owner/repo"` or `"github:owner/repo"` - **Platform shorthands**: `"gitlab:owner/repo"`, `"bitbucket:owner/repo"` - **Gist shorthand**: `"gist:"` - **URL forms**: `git+https://`, `https://`, `git://`, `git@` SSH, `git+ssh://` Returns `undefined` for `null`, `undefined`, arrays, or values that cannot be matched to any recognised format. ```typescript import { parsePackageRepository } from '@helpers4/url'; parsePackageRepository(repository: unknown): PackageRepository | undefined ``` **Parameters:** - `repository: unknown` — The `repository` field value from `package.json`. **Returns:** `PackageRepository | undefined` — A parsed PackageRepository object, or `undefined` if the input cannot be parsed. **Examples:** *Parse the npm canonical object form* Parses the full object form written by npm publish. ```typescript parsePackageRepository({ type: 'git', url: 'git+https://github.com/helpers4/typescript.git' }) // => { type: 'git', host: 'github', slug: 'helpers4/typescript', // owner: 'helpers4', repo: 'typescript', gistId: undefined, directory: undefined } ``` *Parse npm shorthand forms* npm accepts "owner/repo", "github:owner/repo", "gitlab:owner/repo" etc. as shorthand. ```typescript parsePackageRepository('helpers4/typescript') // => { host: 'github', slug: 'helpers4/typescript', owner: 'helpers4', repo: 'typescript', ... } parsePackageRepository('gitlab:myorg/myproject') // => { host: 'gitlab', slug: 'myorg/myproject', owner: 'myorg', repo: 'myproject', ... } parsePackageRepository('gist:11081aaa281') // => { host: 'gist', gistId: '11081aaa281', slug: undefined, owner: undefined, ... } ``` --- ### `relativeURLToAbsolute` Converts a relative URL to an absolute URL using the current document base URI. ```typescript import { relativeURLToAbsolute } from '@helpers4/url'; relativeURLToAbsolute(relativeUrl: string): string ``` **Parameters:** - `relativeUrl: string` — The relative URL to convert **Returns:** `string` — The absolute URL **Examples:** *Convert a relative URL to absolute* Prepends the base URI to a relative path, cleaning duplicate slashes. ```typescript relativeURLToAbsolute('/api/data') // => 'http://localhost/api/data' (depends on document.baseURI) ``` --- ### `withLeadingSlash` Adds a leading slash `/` to the given URL if it is not already present. This function is useful for ensuring that URLs are properly formatted with a leading slash, which is often required in web development for consistency and to avoid issues with relative paths. ```typescript import { withLeadingSlash } from '@helpers4/url'; withLeadingSlash(url: string): string ``` **Parameters:** - `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withLeadingSlash } from '@helpers4/url'; withLeadingSlash(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `undefined` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withLeadingSlash } from '@helpers4/url'; withLeadingSlash(url: null): null ``` **Parameters:** - `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `null` — The URL string with a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Add a leading slash* Ensures the URL starts with a forward slash. ```typescript withLeadingSlash('path/to/resource') // => '/path/to/resource' ``` *Already has leading slash* Does not add a duplicate slash. ```typescript withLeadingSlash('/already/has/slash') // => '/already/has/slash' ``` --- ### `withoutLeadingSlash` Removes the leading slash `/` from the given URL if it is present. This function is useful for ensuring that URLs are properly formatted without a leading slash, which is often required in web development for consistency and to avoid issues with relative paths. ```typescript import { withoutLeadingSlash } from '@helpers4/url'; withoutLeadingSlash(url: string): string ``` **Parameters:** - `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withoutLeadingSlash } from '@helpers4/url'; withoutLeadingSlash(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `undefined` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withoutLeadingSlash } from '@helpers4/url'; withoutLeadingSlash(url: null): null ``` **Parameters:** - `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `null` — The URL string without a leading slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Remove leading slash* Strips the leading slash from a URL path. ```typescript withoutLeadingSlash('/path/to/resource') // => 'path/to/resource' ``` --- ### `withoutTrailingSlash` Removes the trailing slash `/` from the given URL if it is present. This function is useful for ensuring that URLs are properly formatted without a trailing slash, which is often required in web development for consistency and to avoid issues with relative paths. ```typescript import { withoutTrailingSlash } from '@helpers4/url'; withoutTrailingSlash(url: string): string ``` **Parameters:** - `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withoutTrailingSlash } from '@helpers4/url'; withoutTrailingSlash(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `undefined` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withoutTrailingSlash } from '@helpers4/url'; withoutTrailingSlash(url: null): null ``` **Parameters:** - `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `null` — The URL string without a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Remove trailing slash* Strips the trailing slash from a URL path. ```typescript withoutTrailingSlash('path/to/resource/') // => 'path/to/resource' ``` --- ### `withTrailingSlash` Adds a trailing slash `/` to the given URL if it is not already present. This function is useful for ensuring that URLs are properly formatted with a trailing slash, which is often required in web development for consistency and to avoid issues with relative paths. ```typescript import { withTrailingSlash } from '@helpers4/url'; withTrailingSlash(url: string): string ``` **Parameters:** - `url: string` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `string` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withTrailingSlash } from '@helpers4/url'; withTrailingSlash(url: undefined): undefined ``` **Parameters:** - `url: undefined` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `undefined` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. ```typescript import { withTrailingSlash } from '@helpers4/url'; withTrailingSlash(url: null): null ``` **Parameters:** - `url: null` — The URL string to be processed. Can be `string`, `undefined`, or `null`. **Returns:** `null` — The URL string with a trailing slash, or `undefined` if the input is `undefined`, or `null` if the input is `null`. **Examples:** *Add a trailing slash* Ensures the URL ends with a forward slash. ```typescript withTrailingSlash('path/to/resource') // => 'path/to/resource/' ``` --- ## version Package: `@helpers4/version` ### `compare` Compares two semantic version strings according to SemVer 2.0.0 specification Supports: - Core version: MAJOR.MINOR.PATCH - Pre-release: -alpha, -beta.1, -rc.1, etc. - Build metadata: +build, +sha.abc123 (ignored in comparison per spec) - Optional 'v' prefix ```typescript import { compare } from '@helpers4/version'; compare(version1: string, version2: string): number ``` **Parameters:** - `version1: string` — First version string - `version2: string` — Second version string **Returns:** `number` — -1 if version1 < version2, 0 if equal, 1 if version1 > version2 **Examples:** *Compare two semver versions* Returns -1, 0, or 1 based on SemVer ordering. ```typescript compare('1.0.0', '2.0.0') // => -1 compare('1.0.0', '1.0.0') // => 0 compare('2.0.0', '1.0.0') // => 1 ``` *Prerelease is lower than release* A prerelease version is always less than the release. ```typescript compare('1.0.0-alpha', '1.0.0') // => -1 ``` --- ### `increment` Increments a semantic version ```typescript import { increment } from '@helpers4/version'; increment(version: string, type: "major" | "minor" | "patch"): string ``` **Parameters:** - `version: string` — The version to increment - `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch') **Returns:** `string` — Incremented version string ```typescript import { increment } from '@helpers4/version'; increment(version: undefined, type: "major" | "minor" | "patch"): undefined ``` **Parameters:** - `version: undefined` — The version to increment - `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch') **Returns:** `undefined` — Incremented version string ```typescript import { increment } from '@helpers4/version'; increment(version: null, type: "major" | "minor" | "patch"): null ``` **Parameters:** - `version: null` — The version to increment - `type: "major" | "minor" | "patch"` — The increment type ('major', 'minor', 'patch') **Returns:** `null` — Incremented version string **Examples:** *Increment the patch version* Bumps the patch number while keeping major and minor. ```typescript increment('1.2.3', 'patch') // => '1.2.4' ``` *Increment the minor version* Bumps the minor number and resets patch to 0. ```typescript increment('1.2.3', 'minor') // => '1.3.0' ``` *Preserve the v prefix* The v prefix is preserved if present in the input. ```typescript increment('v1.0.0', 'major') // => 'v2.0.0' ``` --- ### `incrementPrerelease` Increments the prerelease portion of a semantic version — the semantics `npm version prerelease --preid ` uses, not covered by increment (which only handles `'major' | 'minor' | 'patch'`). - No current prerelease (a release version) → bumps `patch` and starts a new prerelease line at `.0` (a prerelease of the version itself, e.g. `1.2.3`, would already be released). - Same prerelease type as the current version → increments its counter. - Different prerelease type (e.g. `alpha` → `beta`) → resets the counter to `0`. Input prerelease can be any shape, but only the first two parts are considered; output is always normalized to `.`. Build metadata, if any, is dropped — it's tied to the specific build that produced the input version, not the new one. A leading `v` is preserved if present, matching increment's behavior (`parse`/ `stringify` alone would strip it — see their docs). ```typescript import { incrementPrerelease } from '@helpers4/version'; incrementPrerelease(version: string, prereleaseId: string): string ``` **Parameters:** - `version: string` — The version to increment - `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`) **Returns:** `string` — The version with an incremented or newly-started prerelease ```typescript import { incrementPrerelease } from '@helpers4/version'; incrementPrerelease(version: undefined, prereleaseId: string): undefined ``` **Parameters:** - `version: undefined` — The version to increment - `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`) **Returns:** `undefined` — The version with an incremented or newly-started prerelease ```typescript import { incrementPrerelease } from '@helpers4/version'; incrementPrerelease(version: null, prereleaseId: string): null ``` **Parameters:** - `version: null` — The version to increment - `prereleaseId: string` — The prerelease type/identifier (e.g. `'alpha'`, `'beta'`, `'rc'`) **Returns:** `null` — The version with an incremented or newly-started prerelease **Examples:** *Start a new alpha line* Bumping a release version starts a fresh prerelease at .0, one patch ahead. ```typescript incrementPrerelease('1.2.3', 'alpha') // => '1.2.4-alpha.0' ``` *Increment the same prerelease type* Bumping with the same prereleaseId increments its counter. ```typescript incrementPrerelease('1.2.4-alpha.0', 'alpha') // => '1.2.4-alpha.1' ``` *Switch prerelease type* Switching to a different prereleaseId resets the counter to 0, e.g. graduating from alpha to beta. ```typescript incrementPrerelease('1.2.4-alpha.3', 'beta') // => '1.2.4-beta.0' ``` --- ### `isPrerelease` Returns `true` when the version string has a prerelease suffix (i.e. contains a `-` after the core `MAJOR.MINOR.PATCH`). ```typescript import { isPrerelease } from '@helpers4/version'; isPrerelease(version: string): boolean ``` **Parameters:** - `version: string` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`). **Returns:** `boolean` — `true` if the version is a prerelease, `false` otherwise. ```typescript import { isPrerelease } from '@helpers4/version'; isPrerelease(version: ParsedVersion): boolean ``` **Parameters:** - `version: ParsedVersion` — A ParsedVersion object (as returned by parse). **Returns:** `boolean` — `true` if `version.prerelease` is non-empty, `false` otherwise. ```typescript import { isPrerelease } from '@helpers4/version'; isPrerelease(version: undefined): undefined ``` **Parameters:** - `version: undefined` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`). **Returns:** `undefined` — `true` if the version is a prerelease, `false` otherwise. ```typescript import { isPrerelease } from '@helpers4/version'; isPrerelease(version: null): null ``` **Parameters:** - `version: null` — A semantic version string (e.g. `'2.0.0-alpha.1'`, `'1.0.0'`). **Returns:** `null` — `true` if the version is a prerelease, `false` otherwise. **Examples:** *Detect a prerelease version* Returns true for any version string that contains a prerelease suffix. ```typescript isPrerelease('2.0.0-alpha.1') // true isPrerelease('1.0.0-rc.0') // true ``` *Stable versions return false* Returns false when the version has no prerelease suffix. ```typescript isPrerelease('1.0.0') // false isPrerelease('2.1.3') // false ``` *Accept a ParsedVersion object* Works with the result of parse() — checks the prerelease array instead of string matching. ```typescript isPrerelease(parse('2.0.0-alpha.1')) // true isPrerelease(parse('1.0.0')) // false ``` --- ### `parse` Parses a semantic version string into its components according to SemVer 2.0.0 specification Supports: - Core version: MAJOR.MINOR.PATCH - Pre-release: -alpha, -beta.1, -rc.1, -0.3.7, -x.7.z.92 - Build metadata: +build, +sha.abc123, +20130313144700 - Optional 'v' prefix (commonly used in git tags) ```typescript import { parse } from '@helpers4/version'; parse(version: string): ParsedVersion ``` **Parameters:** - `version: string` — Version string to parse **Returns:** `ParsedVersion` — Parsed version object with major, minor, patch, prerelease, and build ```typescript import { parse } from '@helpers4/version'; parse(version: undefined): undefined ``` **Parameters:** - `version: undefined` — Version string to parse **Returns:** `undefined` — Parsed version object with major, minor, patch, prerelease, and build ```typescript import { parse } from '@helpers4/version'; parse(version: null): null ``` **Parameters:** - `version: null` — Version string to parse **Returns:** `null` — Parsed version object with major, minor, patch, prerelease, and build **Examples:** *Parse a semver string* Breaks a semantic version string into its components. ```typescript parse('1.2.3') // => { major: 1, minor: 2, patch: 3, prerelease: [], build: [] } ``` *Parse a prerelease version* Handles prerelease identifiers and optional v prefix. ```typescript parse('v2.0.0-alpha.1') // => { major: 2, minor: 0, patch: 0, prerelease: ['alpha', '1'], build: [] } ``` --- ### `satisfiesRange` Checks if a version satisfies a range (simple implementation) ```typescript import { satisfiesRange } from '@helpers4/version'; satisfiesRange(version: string, range: string): boolean ``` **Parameters:** - `version: string` — Version to check - `range: string` — Range pattern (e.g., ">=1.0.0", "~1.2.0", "^1.0.0") **Returns:** `boolean` — True if version satisfies the range **Examples:** *Check caret range* Caret (^) allows patch and minor updates within the same major. ```typescript satisfiesRange('1.2.3', '^1.0.0') // => true ``` *Check greater-than-or-equal range* The >= operator checks if the version is at least the specified value. ```typescript satisfiesRange('2.0.0', '>=1.5.0') // => true ``` *Out of range* Returns false when the version does not satisfy the range. ```typescript satisfiesRange('0.9.0', '>=1.0.0') // => false ``` --- ### `stringify` Reconstruct a semantic version string from a ParsedVersion object. This is the inverse of parse: `stringify(parse(v)) === stripV(v)` for any valid SemVer string `v`. ```typescript import { stringify } from '@helpers4/version'; stringify(parsed: ParsedVersion): string ``` **Parameters:** - `parsed: ParsedVersion` — A parsed semantic version object. **Returns:** `string` — The reconstructed version string (without leading `v`). ```typescript import { stringify } from '@helpers4/version'; stringify(parsed: undefined): undefined ``` **Parameters:** - `parsed: undefined` — A parsed semantic version object. **Returns:** `undefined` — The reconstructed version string (without leading `v`). ```typescript import { stringify } from '@helpers4/version'; stringify(parsed: null): null ``` **Parameters:** - `parsed: null` — A parsed semantic version object. **Returns:** `null` — The reconstructed version string (without leading `v`). **Examples:** *Reconstruct a stable version* Converts a ParsedVersion object back to a version string. ```typescript stringify({ major: 1, minor: 2, patch: 3, prerelease: [], build: [] }) // => '1.2.3' ``` *Round-trip with parse* stringify(parse(v)) returns the original version string (without leading v). ```typescript stringify(parse('2.0.0-alpha.1')) // => '2.0.0-alpha.1' stringify(parse('1.0.0-beta+exp.sha.5114f85')) // => '1.0.0-beta+exp.sha.5114f85' ``` --- ### `stripV` Strip the leading "v" from a version string if it exists. ```typescript import { stripV } from '@helpers4/version'; stripV(version: string): string ``` **Parameters:** - `version: string` — The version string to process **Returns:** `string` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v" ```typescript import { stripV } from '@helpers4/version'; stripV(version: null): null ``` **Parameters:** - `version: null` — The version string to process **Returns:** `null` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v" ```typescript import { stripV } from '@helpers4/version'; stripV(version: undefined): undefined ``` **Parameters:** - `version: undefined` — The version string to process **Returns:** `undefined` — The version string without leading "v", or the original value if it's not a string or doesn't start with "v" **Examples:** *Remove v prefix from a version string* Strips the leading "v" from a git tag-style version string. ```typescript stripV('v1.2.3') // => '1.2.3' ``` *No-op when there is no v prefix* Returns the string unchanged when it does not start with "v". ```typescript stripV('1.2.3') // => '1.2.3' ``` ---