Skip to content

Releases: radashi-org/radashi

v12.6.2

20 Aug 19:13
Compare
Choose a tag to compare

Fixed

  • (range) Ensure end parameter works when 0 in 9c8ffa0

v12.6.1

09 Aug 14:11
Compare
Choose a tag to compare

Fixed

  • (group) Use Object.create(null) for the returned object in 5db8c37

v12.6.0

26 Jun 19:56
Compare
Choose a tag to compare

New Functions

Add assert function → PR #403

The assert function from Radashi is used to assert that a given condition is true. If the condition evaluates to false, the function throws an error. This is a fundamental building block for ensuring that certain conditions are met at runtime. This utility is particularly useful in TypeScript for its ability to perform type narrowing.

  • Asserts a condition and throws an error if false.
  • Useful for TypeScript type narrowing using the asserts keyword.
  • Accepts an optional message (string or Error instance) for failed assertions.
  • assert(false, ...) has a never return type for unreachable code paths.
  • Inspired by Node.js's assert module.
import * as _ from 'radashi'

function processValue(value: string | null | undefined) {
  _.assert(value, 'Value cannot be null, undefined, or empty')

  // After the assertion, 'value' is narrowed to type 'string'
  console.log(value.toUpperCase())
}

processValue('hello') // logs "HELLO"
// _.assert throws on falsy values like:
// - null
// - undefined
// - '' (empty string)
// - 0
// - false

🔗 Docs / Source / Tests

Add escapeHTML function → PR #401

Replaces all occurrences of specific characters with their corresponding HTML entities to escape HTML in a string.

  • & is replaced with &
  • < is replaced with <
  • > is replaced with >
  • " is replaced with "
  • ' is replaced with '
import * as _ from 'radashi'

_.escapeHTML(`Sarah said, "5 < 10 & that's obvious."`)
// => 'Sarah said, &quot;5 &lt; 10 &amp; that&#39;s obvious.&quot;'

🔗 Docs / Source / Tests

Add parseDuration function → PR #416

Parses a human-readable duration string (like "1 hour", "2 seconds") into milliseconds.

  • Supports units like millisecond, second, minute, hour, day, and week.
  • Custom units can be added.
  • A DurationParser class is available for more efficient repeated parsing.
import * as _ from 'radashi'

_.parseDuration('1 second') // => 1_000
_.parseDuration('1h') // => 3_600_000
_.parseDuration('1 hour') // => 3_600_000
_.parseDuration('1.5 hours') // => 5_400_000
_.parseDuration('-1h') // => -3_600_000

Thanks to Alec Larson and @hugo082 for their work on this feature!

🔗 Docs / Source / Tests

Add parseQuantity function → PR #416

Parses a quantity string like "2 dollars" into its numeric value. You must provide a unit conversion map, with optional short unit aliases.

  • Requires a unit conversion map.
  • Supports optional short unit aliases.
  • A QuantityParser class is available for more efficient repeated parsing and subclassing.
import * as _ from 'radashi'

const moneyUnits = {
  units: {
    cent: 1,
    dollar: 100,
  },
  short: {
    $: 'dollar',
  },
} as const

_.parseQuantity('1 cent', moneyUnits)
// => 1

_.parseQuantity('2 dollars', moneyUnits)
// => 200

_.parseQuantity('5$', moneyUnits)
// => 500

Thanks to Alec Larson and @hugo082 for their work on this feature!

🔗 Docs / Source / Tests

Add promiseChain function → PR #402

Chain together multiple, potentially asynchronous functions. The result of each function is passed to the next function.

  • Executes functions in the order they are provided.
  • Supports both synchronous and asynchronous functions.
  • Returns a Promise with the final result.
import * as _ from 'radashi'

const func1 = (a, b) => a + b
const func2 = async n => n * 2
const func3 = async n => `Your Value is ${n}`

const chained = _.promiseChain(func1, func2, func3)

await chained(5, 2) // => "Your Value is 14"

Thanks to Bharat Soni for their work on this feature!

🔗 Docs / Source / Tests

Add queueByKey function → PR #407

Wraps an asynchronous function to ensure that calls with the same key are queued and executed sequentially, while calls with different keys can run in parallel. This is useful for preventing race conditions when operations must not overlap for the same logical group (like user ID or resource ID).

  • Sequential per key: Operations with the same key execute one after another
  • Parallel across keys: Operations with different keys run concurrently
  • Error handling: Errors are properly propagated and don't break the queue
  • Memory efficient: Queues are automatically cleaned up when empty
  • Type safe: Full TypeScript support with generic types
import * as _ from 'radashi'

const updateUser = async (userId: string, data: object) => {
  // Simulate API call that shouldn't overlap for the same user
  const response = await fetch(`/api/users/${userId}`, {
    method: 'POST',
    body: JSON.stringify(data),
  })
  return response.json()
}

const queuedUpdate = _.queueByKey(updateUser, userId => userId)

// These will run sequentially for user123
queuedUpdate('user123', { name: 'Alice' })
queuedUpdate('user123', { age: 30 })

// This runs in parallel with user123's queue
queuedUpdate('user456', { name: 'Bob' })

🔗 Docs / Source / Tests

Add Semaphore class → PR #415

A synchronization primitive that allows a limited number of concurrent operations to proceed.

  • Limits the number of concurrent operations.
  • Use acquire() to get a permit and release() to free it.
  • Supports acquiring permits with a specific weight.
  • Pending acquisitions can be aborted using AbortController.
  • All pending and future acquisitions can be rejected using semaphore.reject().
import { Semaphore } from 'radashi'

const semaphore = new Semaphore(2)

const permit = await semaphore.acquire()

permit.release()

Thanks to Alec Larson and @hugo082 for their work on this feature!

🔗 Docs / Source / Tests

New Features

Pass array index to group callback → Commit 6d66395

The callback function provided to the group function now receives the array index as its second argument. This allows for more flexible grouping logic that can take into account the position of elements in the original array.

  • The callback signature is now (item, index) => groupKey.
  • Enables grouping based on element position as well as value.
import * as _ from 'radashi'

const items = ['a', 'b', 'c', 'd', 'e']

const groupedByIndex = _.group(items, (item, index) =>
  index % 2 === 0 ? 'even' : 'odd',
)
// => { even: ['a', 'c', 'e'], odd: ['b', 'd'] }

🔗 Docs / Source / Tests

Fixes

v12.5.1

23 May 21:41
Compare
Choose a tag to compare

Fixed

v12.5.0

15 May 05:18
Compare
Choose a tag to compare

New Functions

Add concat function #388

Flattens and filters nullish values from arguments.

import { concat } from 'radashi'

const result = concat('a', null, ['b', undefined], 'c')
// => ['a', 'b', 'c']

🔗 Docs / Source / Tests

Add pluck function #376

Map an array of objects to an array of arrays.

import { pluck } from 'radashi'

const gods = [
  { name: 'Ra', power: 100 },
  { name: 'Zeus', power: 98 },
]

const names = pluck(gods, ['name'])
// => [['Ra'], ['Zeus']]

Thanks to @nusohiro for the contribution!

🔗 Docs / Source / Tests

Fixed

Fix mapify index argument #384

Ensure the index argument passed to mapify's 2nd callback is the actual array index.

import { mapify } from 'radashi'

const list = [
  { id: 'a', word: 'hello' },
  { id: 'a', word: 'bye' },
  { id: 'a', word: 'oh' },
]

const result = mapify(
  list,
  x => x.id,
  (x, i) => x.word + i,
)
// => Map { 'a' => 'oh2' }

Thanks to @Yukiniro for the contribution!

Avoid infinite loop in cluster when size is 0 #397

The cluster function now correctly handles the case where the size is 0 or less by returning an empty array.

import { cluster } from 'radashi'

const result = cluster([1, 2, 3], 0)
// => []

Thanks to @fResult for the contribution!

Types

Improve cluster type inference #389

The cluster function now provides precise type inference for common cluster sizes (1-8) using tuple types.

import { cluster } from 'radashi'

const result = cluster(['a', 'b', 'c', 'd'], 2)
//    ^? [string, string][]

Thanks to @fResult for the contribution!

v12.4.0

16 Mar 15:57
Compare
Choose a tag to compare

New Functions

Add remove function → PR #344

The remove function removes elements from an array based on the specified predicate function.

  • Removes elements that satisfy the predicate function.
  • Returns a new array with the removed elements.
  • Does not mutate the original array.
import * as _ from 'radashi'

const numbers = [1, 2, 3, 4, 5]
const removed = _.remove(numbers, value => value % 2 === 0)
console.log(removed) // Output: [1, 3, 5]

🔗 Docs / Source / Tests

Thanks to nusohiro for their work on this feature!

Add toResult function → PR #375

The toResult function converts a PromiseLike to a Promise<Result>.

  • Converts a resolved promise to [undefined, value].
  • Converts a rejected promise to [Error, undefined].
  • Rethrows non-Error rejections.
import { toResult, Result } from 'radashi'

const good = async (): Promise<number> => 1
const bad = async (): Promise<number> => {
  throw new Error('bad')
}

const goodResult = await toResult(good())
// => [undefined, 1]

const badResult = await toResult(bad())
// => [Error('bad'), undefined]

🔗 Docs / Source / Tests

Thanks to Alec Larson for their work on this feature!

Add memoLastCall function → PR #353

The memoLastCall function creates a memoized version of a function that caches only its most recent call. This is useful for optimizing expensive calculations when only the latest result needs to be cached, making it more memory-efficient than traditional memoization.

  • Caches the last result of a function call.
  • Returns the cached result if the function is called with the same arguments as the previous call.
  • Optimizes expensive calculations by avoiding recalculation when the arguments are the same.
import * as _ from 'radashi'

const expensiveCalculation = (x: number, y: number): number => {
  console.log('Calculating...')
  return x + y
}

const memoizedCalc = _.memoLastCall(expensiveCalculation)

console.log(memoizedCalc(2, 3)) // Outputs: \"Calculating...\" then 5
console.log(memoizedCalc(2, 3)) // Outputs: 5 (uses cached result)
console.log(memoizedCalc(3, 4)) // Outputs: \"Calculating...\" then 7
console.log(memoizedCalc(2, 3)) // Outputs: \"Calculating...\" then 5 (previous cache was overwritten)

🔗 Docs / Source / Tests

Thanks to Alec Larson for their work on this feature!

Add isAsyncIterable function → PR #366

The isAsyncIterable function checks if a value is an async iterable.

  • Returns true for async iterables created by an async generator function.
  • Returns true for objects with a [Symbol.asyncIterator] method.
  • Returns false for everything else.
import * as _ from 'radashi'

_.isAsyncIterable(
  (async function* () {
    yield 1
  })(),
)
// => true

_.isAsyncIterable([1, 2, 3])
// => false

🔗 Docs / Source / Tests

Thanks to Alec Larson for their work on this feature!

Add isBigInt function → PR #369

The isBigInt function returns true if the given value is a BigInt.

  • Returns true when typeof returns 'bigint'.
  • Returns false for everything else.
import * as _ from 'radashi'

_.isBigInt(0n) // => true
_.isBigInt(BigInt(0)) // => true
_.isBigInt(12) // => false
_.isBigInt('0n') // => false

🔗 Docs / Source / Tests

Thanks to Shan Shaji for their work on this feature!

New Features

BigInt support in isEmpty → PR #374

The isEmpty function now supports BigInt values.

  • Returns true for 0n or BigInt(0).
import * as _ from 'radashi'

_.isEmpty(0n) // => true
_.isEmpty(BigInt(0)) // => true
_.isEmpty(1n) // => false

Thanks to Alec Larson for their work on this feature!

v12.3.4

26 Jan 05:02
Compare
Choose a tag to compare

Fixed

  • (reduce) Align with native reduce behavior + perf improvements by @aleclarson in #341

v12.3.3

11 Jan 18:23
Compare
Choose a tag to compare

Types

v12.3.2

10 Jan 17:42
Compare
Choose a tag to compare

Fixed

  • (shuffle) Correction to Fisher-Yates implementation by @crishoj in #338

v12.3.1

07 Jan 17:48
Compare
Choose a tag to compare

Fixed