v12.7.0
New Functions
Add getOrInsert and getOrInsertComputed functions → PR #444
Access or initialize map entries without boilerplate branching. getOrInsert writes the provided value once, while getOrInsertComputed lazily creates an entry only when it is missing.
- Works with both
MapandWeakMapinstances - Returns the stored entry so you can chain additional logic
- Avoids unnecessary factory calls when a key already exists
import * as _ from 'radashi'
const counts = new Map<string, number>()
_.getOrInsert(counts, 'clicks', 1) // => 1
_.getOrInsert(counts, 'clicks', 5) // => 1
_.getOrInsertComputed(counts, 'views', () => 10) // => 10
_.getOrInsertComputed(counts, 'views', () => 0) // => 10Inspired by TC39's upsert proposal.
🔗 Docs: getOrInsert · getOrInsertComputed / Source: getOrInsert.ts · getOrInsertComputed.ts / Tests: getOrInsert.test.ts · getOrInsertComputed.test.ts
Add isArrayEqual function → PR #417
Compare arrays with Object.is precision. isArrayEqual checks length and element identity, correctly handling tricky cases like NaN, sparse arrays, and the +0/-0 distinction.
- Uses
Object.issoNaNmatches itself while+0and-0stay distinct - Short-circuits when lengths differ for a fast inequality check
- Leaves the original arrays untouched
import * as _ from 'radashi'
_.isArrayEqual([1, 2, 3], [1, 2, 3]) // => true
_.isArrayEqual([0], [-0]) // => false
_.isArrayEqual([Number.NaN], [Number.NaN]) // => trueAdd isMapEqual and isSetEqual functions → PR #437
Quickly compare collections without writing loops. isMapEqual uses isEqual to traverse nested values, while isSetEqual focuses on membership equality for reference types.
- Checks map sizes first, then verifies each key/value pair deeply
- Compares set entries via
Set#has, perfect for shared object references or primitive values - Gives you targeted equality helpers instead of overloading
isEqual
import * as _ from 'radashi'
const left = new Map([
['id', 1],
['tags', ['radashi', 'bench']],
])
const right = new Map([
['tags', ['radashi', 'bench']],
['id', 1],
])
_.isMapEqual(left, right) // => true
const user = { id: 1 }
_.isSetEqual(new Set([user]), new Set([user])) // => true🔗 Docs: isMapEqual · isSetEqual / Source: isMapEqual.ts · isSetEqual.ts / Tests: isMapEqual.test.ts · isSetEqual.test.ts
Add absoluteJitter and proportionalJitter functions → PR #446
Inject randomized noise into numbers for simulations, experiments, or simple variability. Choose an absolute range or a proportional factor depending on the use case.
absoluteJitteroffsets the base value by up to ±offsetproportionalJitterscales jitter by a percentage of the base value- Designed for symmetric distributions using
Math.random()under the hood
import * as _ from 'radashi'
const base = 100
_.absoluteJitter(base, 5) // => between 95 and 105
_.proportionalJitter(base, 0.1) // => between 90 and 110🔗 Docs: absoluteJitter · proportionalJitter / Source: absoluteJitter.ts · proportionalJitter.ts / Tests: absoluteJitter.test.ts · proportionalJitter.test.ts
Add identity function → PR #422
The identity helper simply returns the value you pass in, providing a lightweight default callback for APIs that expect a mapper function.
- Fully generic, so TypeScript infers the original value type
- Handy as a default getter when working with utilities like
sort - Works even when no argument is supplied, returning
undefined
import * as _ from 'radashi'
_.identity() // => undefined
_.identity('radashi') // => 'radashi'
_.identity({ id: 1 }) // => { id: 1 }Thanks to Nano Miratus for adding this functional building block!
New Features
Use identity as the default getter for sort → PR #423
sort now handles raw numeric arrays without a custom getter. When you omit the getter, it falls back to identity, keeping the API ergonomic while preserving the ability to switch to descending order.
- Explicitly pass
_.identitywhen you want to sort descending - Still clones the array, leaving your original list untouched
import * as _ from 'radashi'
const numbers = [2, 0, 1]
_.sort(numbers) // => [0, 1, 2]
_.sort(numbers, _.identity, true) // => [2, 1, 0]Thanks to Nano Miratus for smoothing out this API!
Allow objectify callbacks to read the item index → PR #440
Both getKey and getValue callbacks now receive the item index, making it easy to build composite keys or inject positional data while converting arrays into dictionaries.
- Keep keys unique by appending the index to collisions
- Shape return values with both the item and its position
- Works seamlessly with existing
objectifycall sites
import * as _ from 'radashi'
const list = [
{ id: 'a', word: 'hello' },
{ id: 'b', word: 'bye' },
]
_.objectify(
list,
(item, i) => `${item.id}_${i}`,
(item, i) => `${item.word}-${i}`,
)
// => { a_0: 'hello-0', b_1: 'bye-1' }Thanks to Ronen Barzel for extending objectify!
Preserve tuple types when using min and max getters → PR #436
When you pass a getter to min or max, the helper now returns the original tuple element instead of widening to T | null. That keeps discriminated unions and as const tuples fully typed.
- New overloads ensure non-empty tuples come back as the same literal type
- Keeps
nullout of the result when the tuple has at least one item - Helps TypeScript infer richer shapes in downstream code
import * as _ from 'radashi'
const sizes = [
{ label: 'S', weight: 8 },
{ label: 'XL', weight: 12 },
] as const
const biggest = _.max(sizes, size => size.weight)
// biggest is non-nullable, since sizes is known to never be emptyThanks to Nano Miratus for tightening up the typings!
Documentation
Clarify that unique preserves original ordering → PR #433
The docs now state that unique keeps the first occurrence of each item. Examples and tests were updated to highlight the stable ordering and refreshed copy for clarity.
- Explicitly documents that duplicates keep their earliest entry
- Updates the example data to match the behavior
- Adds a unit test covering order preservation
import * as _ from 'radashi'
const fish = [
{ name: 'Trout', source: 'lake' },
{ name: 'Salmon', source: 'stream' },
{ name: 'Salmon', source: 'river' },
]
_.unique(fish, item => item.name)
// => [Trout, Salmon]Thanks to Ronen Barzel for polishing the documentation!