Releases: radashi-org/radashi
v12.3.0
New Functions
Add isClass
function → PR #239
The isClass
function determines if a value was declared using ES6 class
syntax, distinguishing modern class declarations from traditional constructor functions or other types.
- Only returns
true
for values created with theclass
keyword - Old-style constructor functions will return
false
- Built-in native class constructors (like
Error
) returnfalse
- Works with type narrowing for TypeScript
import * as _ from 'radashi'
class MyClass {
x = 1
}
function OldConstructor() {
this.x = 1
}
// Examples
_.isClass(MyClass) // true
_.isClass(OldConstructor) // false
_.isClass('string') // false
_.isClass(Error) // false
Thanks to Marlon Passos and Alec Larson for their work on this feature!
Add isNullish
function → PR #277
The isNullish
function is a type-checking utility that determines whether a given value is either null
or undefined
. It helps you avoid the typos that an x == null
check is prone to, and it's shorter to write than x === undefined || x === null
.
import * as _ from 'radashi'
// Basic usage examples
_.isNullish(null) // true
_.isNullish(undefined) // true
_.isNullish('') // false
_.isNullish([]) // false
_.isNullish(0) // false
Thanks to Wei Xiaopeng for their work on this feature!
Add cartesianProduct
function → PR #241
The cartesianProduct
function generates all possible combinations of elements from multiple input arrays, creating a new array containing every unique permutation of elements from those arrays.
- Works with any number of input arrays
- Returns an array of arrays representing all combinations
- Preserves the order of input arrays in the resulting combinations
- Can handle arrays of different types
import * as _ from 'radashi'
const colors = ['red', 'blue']
const numbers = [1, 2, 3]
const booleans = [true, false]
// Generate all combinations of colors, numbers, and booleans
const combinations = _.cartesianProduct(colors, numbers, booleans)
Thanks to Yam Borodetsky, Marlon Passos, and Alec Larson for their work on this feature!
Add isUndefined
function → PR #305
The isUndefined
function is a type guard that checks whether a given value is specifically undefined
. It provides a simple and type-safe way to determine if a value has been left unassigned or is explicitly set to undefined
.
- Strictly checks for
undefined
using thetypeof
operator - Can be used for type narrowing in TypeScript
import * as _ from 'radashi'
// Basic usage examples
const result1 = _.isUndefined(undefined) // true
const result2 = _.isUndefined(null) // false
const result3 = _.isUndefined(42) // false
Thanks to RobinBobin for their work on this feature!
Add timeout
function → PR #250
The timeout
function creates a promise that rejects after a specified delay, providing a way to set timeouts for asynchronous operations. It allows customizing the error message or type of error thrown when the timeout occurs.
- Can be used with a default
TimeoutError
or a custom error message/function - Primarily useful with
Promise.race
to add timeout functionality to async tasks
import * as _ from 'radashi'
// Basic usage: reject after 1 second with default TimeoutError
const basicTimeout = _.timeout(1000)
// With custom message
const customMessageTimeout = _.timeout(1000, 'Operation took too long')
// With Promise.race to limit async task duration
const someAsyncTask = async () => {
await _.sleep(5000) // Simulate a long-running task
return 'Task completed'
}
// Will reject after 1 second if task doesn't complete
const racedTask = await Promise.race([
someAsyncTask(),
_.timeout(1000, 'Task exceeded time limit'),
])
Thanks to Marlon Passos and Alec Larson for their work on this feature!
Add dedent
function → PR #120
The dedent
function removes indentation from a multi-line string, making it easy to format and clean up text templates while preserving the relative indentation of embedded content.
- Supports both explicit and auto-detected indentation
- Works with tagged template strings
- Automatically handles multi-line embedded strings
- Removes the first leading and first trailing empty line
- Preserves relative indentation of content
import * as _ from 'radashi'
// Remove auto-detected indentation
const message = _.dedent`
Hello, world!
This is a dedented message.
`
// => 'Hello, world!\nThis is a dedented message.'
// Remove specific indentation
const customMessage = _.dedent('\n Hello\n World!\n\n', ' ')
// => ' Hello\n World!\n'
Thanks to Alec Larson for their work on this feature!
New Features
Add signal
option to retry
→ PR #262
The new feature introduces an optional signal
parameter to both the retry
function, allowing for manual interruption of async operations using AbortController
.
- The
signal
option accepts anAbortController.signal
- When the signal is aborted, no more calls to the callback will be made
- Aborting will throw a
DOMException
with the message "This operation was aborted" - Works consistently across Node.js and browser environments
import * as _ from 'radashi'
const abortController = new AbortController()
const signal = abortController.signal
const promise = _.retry(
{ times: 3, delay: 1000, signal },
async () => await fetchSomeData(),
)
// To abort the operation:
abortController.abort()
Thanks to ts-saidbe.abdiganiev and Alec Larson for their work on this feature!
Add signal
option to parallel
→ PR #262
The latest update adds an optional signal
option to the parallel
function, allowing you to interrupt parallel processing with an AbortController
.
- The
signal
option works with anAbortController.signal
- When aborted, it will throw a
DOMException
with an"AbortError"
name - Interruption only stops future iterations, not in-progress async calls
- Signals are compatible with both browser and Node.js environments
import * as _ from 'radashi'
// Abort a parallel operation
const abortController = new AbortController()
const signal = abortController.signal
const pizzas = await _.parallel(
{ limit: 2, signal },
['pepperoni', 'cheese', 'mushroom'],
async topping => {
return await bakePizzaInWoodFiredOven(topping)
},
)
// Abort the operation if needed
abortController.abort()
Thanks to ts-saidbe.abdiganiev and Alec Larson for their work on this feature!
Tolerate out-of-range parallel
limit → PR #238
The parallel
function now automatically clamps the concurrency limit between 1 and the input array's length, ensuring more predictable and safe parallel processing.
- Previously, passing a limit larger than the array length or less than 1 could cause unexpected behavior
- The limit is now automatically adjusted to be within the valid range
import * as _ from 'radashi'
// Limit will be adjusted to 3 (array length)
const results = await _.parallel(
10,
['item1', 'item2', 'item3'],
async item => {
// Process each item
return item.toUpperCase()
},
)
Thanks to Marlon Passos and [Alec Larson](https://...
v12.2.3
Fixed
- (all) Be more lenient, reduce memory usage by @aleclarson in e6accd8
v12.2.2
Types
-
Export
PromiseWithResolvers
type by @aleclarson in #301 -
Improve
isEmpty
signature by @MarlonPassos-git in #219 -
Improve
draw
signature for non-empty arrays by @crishoj in #153
v12.2.1
Types
-
Improve signature of
shake
by @aleclarson in #293 -
mapValues
index signature handling by @aleclarson in #297 -
Let
zipToObject
receive readonly arrays by @aeharding in #294
New Contributors
- @aeharding made their first contribution in #294
v12.2.0 – Radashi's first stable release
Starting today, you can install [email protected]
from NPM. This is the first stable release of Radashi, which we've been incubating for more than 4 months, starting on June 23.
Now, we look forward to merging more pull requests, attracting more users, and inspiring more talented folks to join us in building (and just as important, maintaining) the next-generation utility toolkit that is Radashi.
Lastly, I'd like to ask everyone to check out the proposed features open for discussion, and we hope you'll share with us any ideas you may have as well. Remember, this is a community effort. Your perspective is valuable and it will help us make Radashi better with your needs in mind.
New Functions
Add cloneDeep
function → PR #81
The cloneDeep
function creates a deep copy of an object or array.
- It supports cloning of plain objects, arrays,
Map
instances, andSet
instances by default. - The default behavior can be customized by providing a partial
CloningStrategy
implementation.
import * as _ from 'radashi'
const obj = { a: 1, b: { c: 2 } }
const clone = _.cloneDeep(obj)
// The clone and its nested objects have their own identity. Therefore,
// mutating them won't affect the original object, and vice versa.
assert(clone !== obj)
assert(clone.b !== obj.b)
assert(JSON.stringify(clone) === JSON.stringify(obj))
Add once
function → PR #80
Create a wrapper around a given function such that it executes at most once.
- Subsequent calls to the wrapped function return the result from the first execution, regardless of the arguments provided. This behavior is akin to “memoization” but specifically designed for single-use functions.
- Use
_.once.reset(fn)
to clear the stored result and allow the function to execute again.
import * as _ from 'radashi'
const fn = _.once(() => Math.random())
fn() // 0.5
fn() // 0.5
_.once.reset(fn)
fn() // 0.8
Cast a non-nullish value into an array → PR #97
The castArrayIfExists
function ensures that a non-nullish input value is always returned as an array.
- If the input is already an array, it returns a shallow copy of the array.
- If the input is not an array, it wraps the input in a new array.
- Nullish values (null or undefined) are passed through as is.
import * as _ from 'radashi'
_.castArrayIfExists(1) // => [1]
_.castArrayIfExists([1, 2, 3]) // => [1, 2, 3]
_.castArrayIfExists('hello') // => ['hello']
_.castArrayIfExists(null) // => null
_.castArrayIfExists(undefined) // => undefined
Cast a value into an array → PR #97
The castArray
function ensures that the input value is always returned as an array.
- If the input is already an array, it returns a shallow copy of the array.
- If the input is not an array, it wraps the input in a new array.
import * as _ from 'radashi'
_.castArray(1) // => [1]
_.castArray([1, 2, 3]) // => [1, 2, 3]
_.castArray('hello') // => ['hello']
Convert an array to a map → PR #58
The mapify
function allows you to convert an array into a Map
object, where the keys and values are determined by provided functions.
- The first callback determines the keys of the map.
- The second callback determines the values of the map. If not provided, the original array elements are used as values.
import * as _ from 'radashi'
const fish = [
{ name: 'Marlin', weight: 105 },
{ name: 'Bass', weight: 8 },
{ name: 'Trout', weight: 13 },
]
const fishByName = _.mapify(fish, f => f.name)
// => Map(3) {'Marlin' => { name: 'Marlin', weight: 105 }, 'Bass' => { name: 'Bass', weight: 8 }, 'Trout' => { name: 'Trout', weight: 13 }}
const fishWeightByName = _.mapify(
fish,
f => f.name,
f => f.weight,
)
// => Map(3) { 'Marlin' => 105, 'Bass' => 8, 'Trout' => 13 }
Round a number to a specified precision → PR #53
The round
function allows you to round a number to a specified precision.
- The precision can be a positive or negative integer.
- An optional rounding function (e.g.
Math.floor
orMath.ceil
) can be provided. The default rounding function isMath.round
.
import * as _ from 'radashi'
_.round(123.456) // => 123
_.round(1234.56, -2) // => 1200
_.round(4.001, 2, Math.ceil) // => 4.01
_.round(4.089, 2, Math.floor) // => 4.08
Allow deep traversal of objects and arrays → PR #59
The traverse
function recursively visits each property of an object (or each element of an array) and its nested objects or arrays.
- By default, the only nested objects to be traversed are plain objects and arrays.
- Traversal is performed in a depth-first manner, and circular references are skipped.
- The traversal can be customized with a
TraverseOptions
object.
import * as _ from 'radashi'
const root = { a: { b: 2 }, c: [1, 2] }
_.traverse(root, (value, key, parent, context) => {
const indent = ' '.repeat(context.parents.length)
console.log(`${indent}${key} => ${value}`)
})
// Logs the following:
// a => { b: 2 }
// b => 2
// c => [1, 2]
// 0 => 1
// 1 => 2
Calculate string similarity → PR #122
The similarity
function calculates the Levenshtein distance between two input strings, which represents the minimum number of single-character edits (insertions, deletions, or substitutions) required to change one string into the other.
- A lower number indicates higher similarity, with 0 meaning the strings are identical.
- The comparison is both case-sensitive and whitespace-significant.
- The argument order doesn't matter, as it's symmetric.
import * as _ from 'radashi'
// Identical strings
_.similarity('hello', 'hello') // => 0
// One character difference
_.similarity('kitten', 'sitten') // => 1
// Multiple differences
_.similarity('saturday', 'sunday') // => 3
Cast a value into a comparator function → PR #34
The castComparator
function allows you to create a comparator function that can be passed into Array.prototype.sort
.
Parameters:
mapping
is either a property name or a mapping function.compare
is an optional custom compare function (e.g. forlocaleCompare
use cases).reverse
reverses the comparison order if set totrue
.
import * as _ from 'radashi'
const users = [
{ id: 1, firstName: 'Alice', lastName: 'Smith' },
{ id: 3, firstName: 'Charlie', lastName: 'Brown' },
{ id: 2, firstName: 'Drew', lastName: 'Johnson' },
]
const compareById = _.castComparator('id')
users.sort(compareById)
// [Alice, Drew, Charlie]
const compareByFullName = _.castComparator(
user => `${user.firstName} ${user.lastName}`,
(a, b) => b.localeCompare(a),
)
users.sort(compareByFullName)
// [Alice, Charlie, Drew]
const compareByFullNameReversed = _.castComparator(
user => `${user.firstName} ${user.lastName}`,
(a, b) => b.localeCompare(a),
true,
)
users.sort(compareByFullNameReversed)
// [Drew, Charlie, Alice]