Changelog
All notable changes to @mongez/reinforcements are documented in this file.
3.2.0
Added
retry—shouldRetrypredicate. Bail out of the retry loop early on non-retryable errors (4xx / validation). Returningfalsestops immediately and throws the current error, with no further attempts or delay. Async-capable. Runs afteronError(observe, then decide).retry— jitter.jitter: "full" | "equal" | truerandomises each delay ("full"=random(0, delay),"equal"=delay/2 + random(0, delay/2)) to avoid thundering herd. Draws from the seedableRandom, so schedules stay reproducible underRandom.seed(n).retry—maxDelay. Caps the computed delay (after backoff, before jitter) so exponential backoff can’t grow without bound.retry— function-formbackoff.backoff: (attempt, baseDelay) => numberfor custom strategies (decorrelated jitter, Fibonacci, capped-exponential) beyond the built-in"linear" | "exponential".retry—AbortSignalsupport. Passsignalto cancel between or during attempts; a pending delay is raced against the signal so cancellation resolves promptly.retryable(fn, options?)— new export. Pre-binds retry options to a function, returning a reusable wrapper.
All additions are optional with defaults preserving the previous behaviour — no breaking change.
Tests
retry.test.tsexpanded to 14 tests covering each option, seeded-jitter determinism, abort timing, and asyncshouldRetry.
3.1.0
Added
compact(value, options?)— strip “empty” entries from an object or array. Default predicate dropsnull,undefined, and""; keeps0/false/NaN. Empty arrays/objects are dropped, and recursion is enabled by default — parents that become empty after recursion are themselves dropped. Customisable via{ predicate, empties, deep }. Typical use: cleaning API payloads, query strings, form data.pProps(object)— parallel object destructuring (modelled on Bluebird’sPromise.props). Resolves an object of promises to an object of unwrapped values with the same keys, with full type inference. Non-promise values pass through. Joins the existingpAll/pMap/pSeries/pFilterasync family.
Tests
- 96 test files, 372 tests, all passing (+2 files / +18 tests over 3.0.0).
3.0.0
Breaking
- Tests migrated from Jest to Vitest. Internal change for contributors; consumers are unaffected.
jest/ts-jest/@types/jest/jest-esm-jsx-transformremoved from devDependencies. only→pick,except→omit(community-standard names).onlyandexceptremain exported as@deprecatedaliases and will be removed in a future major.flattensignature changed from positional args(obj, separator?, keepNested?, parent?, root?)to options-object(obj, { separator?, keepNested?, maxDepth? }). Internal recursion args (parent,root) are no longer part of the public API.mergeis now sane around nullish inputs (merge(null, { a: 1 })returns{ a: 1 }, notnull) and supports array merging strategy via final{ arrays: "replace" | "concat" | "union" }options.getno longer returns the raw literal key when an object happens to have a key matching the dot-notation path; it always walks the path. Falsy values resolve as themselves rather than triggering the default.setnow creates arrays (rather than objects) when an intermediate segment is missing and the next segment is a numeric index.sortnow operates only on plain objects (matches its actual scope; arrays of objects are out of scope — use a collections-packagesortBy).areEqualis a real non-mutating deep-equal. Arrays now respect order ([1,2,3]≠[3,2,1]).shuffleuses Fisher–Yates and is non-mutating by default; pass{ mutate: true }for in-place.clonehandlesRegExp,Error,Map,Set, typed arrays, and circular references via WeakMap memo. Class instances are still preserved by reference (cloning custom constructors is out of scope).toCamelCase/toSnakeCase/toKebabCase/toStudlyCaserebuilt on a sharedwords()normalizer. Fixes the acronym-eating bug:toSnakeCase("AIAgent")is now"ai_agent"(previously"agent").XMLHttpRequest,parseURL,IOError, etc. now produce the expected output.roundactually rounds (wasMath.floorpreviously).debouncenow exposescancel(),flush(),pending(), and supports{ leading, trailing, maxWait }. Removed dead module-leveltimeoutId.Random.color()pads to six hex digits (no more#fffcollapse).Random.date()signature is now{ min?, max? }options.Random.integer/Random.booleanduplicate aliases removed (useint/bool).ARABIC_PATTERNrenamed toARABIC_REGEX;ARABIC_PATTERNkept as deprecated re-export.
Added
Types
Path<T>,PathValue<T, P>— typed dot-notation autocomplete.DeepPartial,DeepRequired,DeepReadonly,DeepMutable.Prettify,UnionToIntersection,Branded.Nullable<T>,Maybe<T>,Awaitable<T>,NonEmptyArray<T>.
Object
pick,omit(new canonical names foronly/except).has(obj, path)— counterpart toget/set.mapValues,mapKeys.entries,fromEntries,keys,values— typed wrappers.invert(obj)— swap keys and values.defaults(target, ...sources)— fill undefined keys.walk(obj, visitor)— recursive leaf traversal.diff(a, b)—{ added, removed, changed }structural diff.freeze(obj)— recursiveObject.freeze.
String
words(str)— shared semantic tokenizer powering all casings.toPascalCase(alias oftoStudlyCasewith the common name).toTitleCase,toConstantCase,toDotCase,toPathCase.slugify(str, options)— URL-safe, diacritic-stripping.truncate(str, length, { suffix, byWord, position }).pad,padStart,padEnd— typed helpers.escapeHtml,unescapeHtml.mask(str, { start, end, char })— middle-masking.template(str, vars)—{path}interpolation with dot-notation.wordCount,charCount(unicode-aware option).reverse(str)— unicode-safe.containsArabic.
Number
ceil,floorwith precision (companions to fixedround).clamp(value, min, max).inRange(value, min, max, options).lerp(a, b, t).toFixed(value, precision)— returns number, not string.formatBytes(bytes, { decimals, binary }).formatNumber(value, { locale, ...IntlOptions }).percentage(value, total, decimals).safeDivide(a, b, fallback).parseNumber(value, fallback).
Function / Utils
throttle(fn, wait, options)— leading/trailing controls.memoize(fn, { resolver, ttl }).once,after(n, fn),before(n, fn).pipe(value, ...fns),compose(...fns).tap(value, fn)andtap.with(fn).curry,partial,partialRight.noop,identity,constant,negate.
Async (new namespace)
sleep(ms, value?).retry(fn, { attempts, delay, backoff, onError }).timeout(promise, ms, message?).pAll,pAllSettled— typed tuple-preserving wrappers.pMap(items, mapper, { concurrency, stopOnError }).pSeries,pFilter.defer<T>()— externally-resolvable promise.debounceAsync(fn, wait).
Random
Random.pick,Random.sample,Random.weighted.Random.uuid()(usescrypto.randomUUIDwhen available).Random.nanoid(size),Random.token(bytes)(crypto-backed where possible).Random.float(min, max, precision?).Random.seed(seed)— mulberry32 reproducible mode.
Lazy (existing API kept, additions only)
lazy.async(producer)— async lazy reference.lazy.from(value)— pre-resolved lazy.type LazyAsync<T>.
Mixed
coalesce(...values)— first non-nullish.