Lazy iterator pipeline built on native ES2025 Iterator helpers with fallback generators.
import { Iterable } from '@neabyte/utils-core'
// From any iterable
const result = Iterable.from([1, 2, 3, 4, 5])
.map(n => n * 2)
.filter(n => n > 4)
.take(2)
.toArray()
// result: [6, 8]Wrap any iterable in a lazy pipe.
Iterable.from([1, 2, 3])
Iterable.from(new Set([1, 2, 3]))
Iterable.from(new Map([['a', 1]]).values())
Iterable.from(
(function* () {
yield 1
yield 2
})()
)Generate a numeric range.
// [0, 1, 2, 3, 4]
Iterable.range(5).toArray()
// [2, 3, 4]
Iterable.range(2, 5).toArray()
// [10, 8, 6, 4, 2]
Iterable.range(10, 0, -2).toArray()Repeat a value indefinitely or a fixed number of times.
// ['x', 'x', 'x']
Iterable.repeat('x', 3).toArray()
// [0, 0, 0, 0, 0]
Iterable.repeat(0).take(5).toArray()Zip multiple iterables together, stopping at the shortest. Supports typed overloads for 2, 3, and 4 iterables.
// [[1, 'a'], [2, 'b']]
Iterable.zip([1, 2, 3], ['a', 'b']).toArray()
// [[1, 'a', true], [2, 'b', false]]
Iterable.zip([1, 2], ['a', 'b'], [true, false]).toArray()Create an empty pipe.
// []
Iterable.empty<number>().toArray()These methods return a new Pipe and do not consume the source until a terminal method is called.
Transform each element.
// [2, 4, 6]
Iterable.from([1, 2, 3])
.map(n => n * 2)
.toArray()Keep elements matching the predicate.
// [2, 4]
Iterable.from([1, 2, 3, 4])
.filter(n => n % 2 === 0)
.toArray()Take only the first count elements.
// [1, 2]
Iterable.from([1, 2, 3, 4]).take(2).toArray()Note
Safe with infinite iterables: Iterable.repeat(1).take(100) works fine.
Skip the first count elements.
// [3, 4]
Iterable.from([1, 2, 3, 4]).drop(2).toArray()Map each element to an iterable, then flatten one level.
// [1, 10, 2, 20]
Iterable.from([1, 2])
.flatMap(n => [n, n * 10])
.toArray()Group elements into arrays of size.
// [[1, 2], [3, 4], [5]]
Iterable.from([1, 2, 3, 4, 5]).chunk(2).toArray()Pair each element with its index.
// [[0, 'a'], [1, 'b']]
Iterable.from(['a', 'b']).enumerate().toArray()Remove duplicates. Without arguments, uses Set equality. With a keyFn, deduplicates by computed key.
// [1, 2, 3]
Iterable.from([1, 2, 2, 3, 1, 2]).distinct().toArray()
// Deduplicate objects by a key
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice (dup)' }
]
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
Iterable.from(users)
.distinct(u => u.id)
.toArray()Execute a side effect without transforming values.
Iterable.from([1, 2, 3])
.tap(n => console.log('processing:', n))
.map(n => n * 2)
.toArray()Append one or more iterables to the end of the pipe. The source is yielded first, then each other in order.
// [1, 2, 3, 4, 5, 6]
Iterable.from([1, 2, 3]).concat([4, 5], [6]).toArray()Skip elements from the start while the predicate returns true. Once the predicate returns false, every remaining element is yielded without re-checking.
// [3, 4, 1, 2]
Iterable.from([1, 2, 3, 4, 1, 2]).dropWhile(n => n < 3).toArray()Yield elements from the start while the predicate returns true. Stops on the first element that fails the predicate.
// [1, 2]
Iterable.from([1, 2, 3, 1]).takeWhile(n => n < 3).toArray()Emit each running accumulator value produced by applying fn to the previous accumulator and the next element, starting from seed. Unlike reduce, every intermediate value is yielded.
// [1, 3, 6, 10]
Iterable.from([1, 2, 3, 4]).scan((sum, n) => sum + n, 0).toArray()Collect all elements into an array, sort them with the optional comparator, and wrap the sorted array in a new pipe. The original source is not modified.
// [1, 2, 3, 4]
Iterable.from([3, 1, 4, 2]).toSorted().toArray()
// [4, 3, 2, 1]
Iterable.from([3, 1, 4, 2]).toSorted((a, b) => b - a).toArray()These methods consume the iterator and return a concrete value.
Collect all elements into an array.
// [1, 2, 3]
Iterable.from([1, 2, 3]).toArray()Execute a side effect for each element.
Iterable.from([1, 2, 3]).forEach(n => console.log(n))Reduce to a single value.
// 6
Iterable.from([1, 2, 3]).reduce((sum, n) => sum + n, 0)Return true if any element matches.
// true
Iterable.from([1, 2, 3]).some(n => n > 2)Note
Short-circuits on first match.
Return true if all elements match.
// true
Iterable.from([2, 4, 6]).every(n => n % 2 === 0)Note
Short-circuits on first mismatch.
Return the first matching element.
// 2
Iterable.from([1, 2, 3]).find(n => n > 1)Return the first element.
// 1
Iterable.from([1, 2, 3]).first()
// undefined
Iterable.empty<number>().first()Return the last element.
// 3
Iterable.from([1, 2, 3]).last()Count the elements.
// 3
Iterable.from([1, 2, 3]).count()Group elements by a key function into a Map.
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
]
// Map { 'admin' => [Alice, Charlie], 'user' => [Bob] }
const byRole = Iterable.from(users).groupBy(u => u.role)Split elements into two arrays. The first array contains every element for which the predicate returned true, and the second array contains the rest. Both arrays preserve the original order.
// [[2, 4], [1, 3, 5]]
Iterable.from([1, 2, 3, 4, 5]).partition(n => n % 2 === 0)Return true if the iterable has no elements.
// true
Iterable.empty<number>().isEmpty()
// false
Iterable.from([1]).isEmpty()Join elements into a string.
// '1-2-3'
Iterable.from([1, 2, 3]).join('-')Collect all elements into a Set.
// Set { 1, 2, 3 }
Iterable.from([1, 2, 3, 2]).toSet()Collect elements into a Map using a key function and optional value function.
const users = [
{ id: 'a', name: 'Alice' },
{ id: 'b', name: 'Bob' }
]
// Map { 'a' => { id: 'a', name: 'Alice' }, 'b' => { id: 'b', name: 'Bob' } }
Iterable.from(users).toMap(u => u.id)
// Map { 'a' => 'Alice', 'b' => 'Bob' }
Iterable.from(users).toMap(u => u.id, u => u.name)Pipes implement the iterable protocol, so they work with for...of, spread, and destructuring.
const pipe = Iterable.from([1, 2, 3]).map(n => n * 10)
for (const value of pipe) {
console.log(value)
}
const [first, second] = Iterable.range(5)