npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jenbuska/yielded

v2.1.0

Published

A TypeScript library for composing and transforming values from synchronous iterables and asynchronous generators through a uniform, lazy-evaluated pipeline API

Downloads

53

Readme

Yielded

A TypeScript library for composing and transforming values from synchronous iterables and asynchronous generators through a uniform, lazy-evaluated pipeline API.

CI TypeScript Node npm Zero Dependencies

Features

  • 🔄 Lazy Evaluation - Operations don't process the entire sequence up front; each value flows through the pipeline one at a time
  • 🚀 Parallel Processing - Built-in support for concurrent async operations with configurable concurrency limits
  • 🔗 Unified API - Same familiar API for both sync iterables and async iterators
  • 📦 Native Iterator Extension - Extends the native Iterator API with additional utilities
  • 🛡️ Type-Safe - Full TypeScript support with comprehensive type inference
  • 🎯 Cancelable - Integrated AbortSignal support for canceling async operations
  • 🌊 Composable - Chain multiple operations together for complex data transformations
  • 🪶 Zero Dependencies - No external runtime dependencies; installs nothing beyond the package itself

Compatibility

Node.js Requirements

This library requires Node.js 20.4.0 or newer (Node 20, 22, 24 LTS releases are all supported and tested in CI).

Why 20.4.0? The runtime cleanup mechanism relies on Symbol.dispose and Symbol.asyncDispose, which were introduced in Node.js 20.4.0. The published bundle no longer uses the using keyword (it was replaced with try/finally), so older versions of Node that didn't support using now work fine.

Browser Support:

  • Chrome 90+ (full ES2022+ support)
  • Firefox 88+ (full ES2022+ support)
  • Edge 90+ (full ES2022+ support)
  • Safari — tested and working via WebKit (ES2022+ supported in modern Safari releases)

The published bundle targets ES2022 and has no runtime dependencies, so it works in all modern browsers without additional configuration.

TypeScript Configuration

No special TypeScript lib settings are needed specifically for this package. The table below shows what standard configs already cover everything:

| Environment | Recommended lib | Notes | |-------------|-------------------|-------| | Node.js | ["ES2022"] or ["ESNext"] | AbortSignal (for withSignal) is included via @types/node | | Browser (React, Vue, etc.) | ["ES2022", "DOM"] | Standard browser config; AbortSignal comes from the DOM lib |

DOM.Iterable is NOT required by @jenbuska/yielded.
You may see it in the examples/react-vite project — that is standard boilerplate for Vite browser apps (it adds Symbol.iterator support to DOM APIs such as NodeList and HTMLCollection) and has nothing to do with this library's types. Include it in your own config if your application code iterates over DOM collections directly.

Installation

npm install @jenbuska/yielded

Quick Start

import { Yielded } from "@jenbuska/yielded";

// Simple transformation pipeline
const result = Yielded.from([1, 2, 3, 4, 5])
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .toArray();
// => [4, 8]

// Async data processing with parallel operations
const customers = await Yielded.from(contractorIds)
  .parallel(5) // Process up to 5 at a time
  .flatMap(async id => await fetchCustomers(id))
  .awaited() // Back to sequential processing
  .filter(customer => customer.isActive)
  .sorted((a, b) => a.name.localeCompare(b.name))
  .take(10)
  .toArray();

Core Concepts

Lazy Evaluation

Operations in Yielded are lazy - they don't execute until you consume the result. This means you can chain multiple transformations efficiently without creating intermediate arrays:

Yielded.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  .filter(n => n % 2 === 0)  // Not executed yet
  .map(n => n * 2)            // Not executed yet
  .take(3)                    // Not executed yet
  .toArray();                 // NOW all operations execute
// => [4, 8, 12]

Creating Yielded Instances

// From arrays
Yielded.from([1, 2, 3])

// From any iterable
Yielded.from(new Set([1, 2, 3]))
Yielded.from("hello") // chars: 'h', 'e', 'l', 'l', 'o'

// From async iterables
Yielded.from(asyncGenerator())

// From promises
Yielded.from(Promise.resolve([1, 2, 3]))

API Reference

Transformation Operations

Transform values as they flow through the pipeline:

map(mapper)

Transform each value with a mapping function.

Yielded.from([1, 2, 3])
  .map(n => n * 2)
  .toArray()
// => [2, 4, 6]

filter(predicate)

Keep only values that pass the predicate test.

Yielded.from([1, 2, 3, 4, 5])
  .filter(n => n % 2 === 0)
  .toArray()
// => [2, 4]

flatMap(mapper)

Map each value to an array/iterable and flatten the results.

Yielded.from([1, 2, 3])
  .flatMap(n => [n, n * 2])
  .toArray()
// => [1, 2, 2, 4, 3, 6]

flat(depth?)

Flatten nested arrays up to the specified depth (default: 1).

Yielded.from([[1, 2], [3, [4, 5]]])
  .flat()
  .toArray()
// => [1, 2, 3, [4, 5]]

Slicing Operations

Control which values pass through:

take(count)

Take only the first count values.

Yielded.from([1, 2, 3, 4, 5])
  .take(3)
  .toArray()
// => [1, 2, 3]

drop(count)

Skip the first count values.

Yielded.from([1, 2, 3, 4, 5])
  .drop(2)
  .toArray()
// => [3, 4, 5]

takeLast(count)

Take only the last count values.

Yielded.from([1, 2, 3, 4, 5])
  .takeLast(2)
  .toArray()
// => [4, 5]

dropLast(count)

Drop the last count values.

Yielded.from([1, 2, 3, 4, 5])
  .dropLast(2)
  .toArray()
// => [1, 2, 3]

takeWhile(predicate)

Take values while the predicate returns true, stop at the first false.

Yielded.from([2, 4, 6, 7, 8])
  .takeWhile(n => n % 2 === 0)
  .toArray()
// => [2, 4, 6]

Sorting and Ordering

sorted(comparator?)

Sort all values using an optional comparator function.

Yielded.from([3, 1, 4, 1, 5])
  .sorted()
  .toArray()
// => [1, 1, 3, 4, 5]

Yielded.from(['banana', 'apple', 'cherry'])
  .sorted((a, b) => a.localeCompare(b))
  .toArray()
// => ['apple', 'banana', 'cherry']

reversed()

Reverse the order of all values.

Yielded.from([1, 2, 3])
  .reversed()
  .toArray()
// => [3, 2, 1]

Batching and Grouping

batch(size)

Group values into arrays of the specified size.

Yielded.from([1, 2, 3, 4, 5, 6, 7])
  .batch(3)
  .toArray()
// => [[1, 2, 3], [4, 5, 6], [7]]

chunkBy(predicate)

Group consecutive values while the predicate returns the same key.

Yielded.from([1, 2, 2, 3, 3, 3, 4])
  .chunkBy(n => n)
  .toArray()
// => [[1], [2, 2], [3, 3, 3], [4]]

groupBy(keySelector, groups?)

Group all values by the result of the key selector function.

Yielded.from([
  { type: 'fruit', name: 'apple' },
  { type: 'vegetable', name: 'carrot' },
  { type: 'fruit', name: 'banana' }
])
  .groupBy(item => item.type)
// => {
//   fruit: [{ type: 'fruit', name: 'apple' }, { type: 'fruit', name: 'banana' }],
//   vegetable: [{ type: 'vegetable', name: 'carrot' }]
// }

Async and Parallel Operations

parallel(concurrency)

Process async operations with limited concurrency. Returns a ParallelYielded instance.

Note: concurrency must be an integer between 1 and 50 (inclusive).

await Yielded.from([1, 2, 3, 4, 5])
  .parallel(2) // Max 2 concurrent operations
  .flatMap(async n => {
    await delay(100);
    return n * 2;
  })
  .awaited()
  .toArray()
// => [2, 4, 6, 8, 10]

awaited()

Convert parallel or async operations back to sequential async processing.

Yielded.from([Promise.resolve(1), Promise.resolve(2)])
  .awaited()
  .map(n => n * 2)
  .toArray()

Utility Operations

tap(callback)

Execute a side effect for each value without modifying the stream.

Yielded.from([1, 2, 3])
  .tap(n => console.log('Processing:', n))
  .map(n => n * 2)
  .toArray()
// Logs: Processing: 1, Processing: 2, Processing: 3
// => [2, 4, 6]

mapPairwise(mapper)

Transform consecutive pairs of values.

Yielded.from([1, 2, 3, 4])
  .mapPairwise((prev, next) => next - prev)
  .toArray()
// => [1, 1, 1]

lift(middleware)

Apply a custom generator transformation.

Yielded.from([1, 2, 3])
  .lift(function*(gen) {
    for (const value of gen) {
      yield value;
      yield value * 10;
    }
  })
  .toArray()
// => [1, 10, 2, 20, 3, 30]

withSignal(signal)

Attach an AbortSignal to cancel async operations.

const controller = new AbortController();
const promise = Yielded.from(hugeDataset)
  .parallel(10)
  .flatMap(async item => await processItem(item))
  .withSignal(controller.signal)
  .toArray();

// Later: cancel the operation
controller.abort();

Terminal Operations (Resolvers)

These operations consume the iterator and return a final result:

toArray()

Collect all values into an array.

Yielded.from([1, 2, 3]).toArray()
// => [1, 2, 3]

toSet()

Collect all values into a Set.

Yielded.from([1, 2, 2, 3]).toSet()
// => Set { 1, 2, 3 }

toSorted(comparator?)

Return a sorted array without modifying the original.

Yielded.from([3, 1, 2]).toSorted()
// => [1, 2, 3]

toReversed()

Return a reversed array.

Yielded.from([1, 2, 3]).toReversed()
// => [3, 2, 1]

reduce(reducer, initialValue?)

Reduce all values to a single value.

Note: Without initialValue, throws a TypeError if the iterator is empty.

Yielded.from([1, 2, 3, 4, 5])
  .reduce((sum, n) => sum + n, 0)
// => 15

Yielded.from([1, 2, 3])
  .reduce((max, n) => Math.max(max, n))
// => 3

forEach(callback)

Execute a function for each value.

Yielded.from([1, 2, 3])
  .forEach((n, index) => console.log(`${index}: ${n}`))
// Logs: 0: 1, 1: 2, 2: 3

consume()

Consume all values without collecting them (useful for side effects).

Yielded.from([1, 2, 3])
  .tap(n => saveToDatabase(n))
  .consume()

find(predicate)

Find the first value that passes the predicate.

Yielded.from([1, 2, 3, 4, 5])
  .find(n => n > 3)
// => 4

some(predicate)

Check if any value passes the predicate.

Yielded.from([1, 2, 3, 4, 5])
  .some(n => n > 3)
// => true

every(predicate)

Check if all values pass the predicate.

Yielded.from([2, 4, 6, 8])
  .every(n => n % 2 === 0)
// => true

first(defaultValue?)

Get the first value.

Note: Without defaultValue, throws a TypeError if the iterator is empty.

Yielded.from([1, 2, 3]).first()
// => 1

Yielded.from([]).first(0)
// => 0

Yielded.from([]).first()
// Throws TypeError

last(defaultValue?)

Get the last value.

Note: Without defaultValue, throws a TypeError if the iterator is empty.

Yielded.from([1, 2, 3]).last()
// => 3

Yielded.from([]).last(0)
// => 0

Yielded.from([]).last()
// Throws TypeError

count()

Count the total number of values.

Yielded.from([1, 2, 3, 4, 5]).count()
// => 5

sumBy(selector?)

Sum all values, optionally using a selector function.

Yielded.from([1, 2, 3, 4, 5]).sumBy()
// => 15

Yielded.from([{x: 1}, {x: 2}, {x: 3}]).sumBy(obj => obj.x)
// => 6

minBy(selector?, defaultValue?)

Find the minimum value, optionally using a selector function.

Note: Without defaultValue, throws a TypeError if the iterator is empty.

Yielded.from([3, 1, 4, 1, 5]).minBy(n => n)
// => 1

Yielded.from([{x: 3}, {x: 1}, {x: 2}]).minBy(obj => obj.x)
// => {x: 1}

Yielded.from<{x: number}>([]).minBy(obj => obj.x, 0)
// => 0

Yielded.from<{x: number}>([]).minBy(obj => obj.x)
// Throws TypeError

maxBy(selector?, defaultValue?)

Find the maximum value, optionally using a selector function.

Note: Without defaultValue, throws a TypeError if the iterator is empty.

Yielded.from([3, 1, 4, 1, 5]).maxBy()
// => 5

Yielded.from([{x: 3}, {x: 1}, {x: 2}]).maxBy(obj => obj.x)
// => {x: 3}

Yielded.from<{x: number}>([]).maxBy(obj => obj.x, 0)
// => 0

Yielded.from<{x: number}>([]).maxBy((obj) => obj.x)
// Throws TypeError

Advanced Examples

Real-World Data Processing

import { Yielded } from "@jenbuska/yielded";

// Paginated API data fetching with parallel processing
async function getCustomersForOrganization(
  organizationId: string,
  pagination: PaginationArgs,
  signal?: AbortSignal
) {
  const { page, pageSize, sortBy, sortDirection } = pagination;
  
  return Yielded.from(getContractors(organizationId))
    // Allow up to 5 concurrent API calls
    .parallel(5)
    .flatMap(async (contractor) => {
      const customers = await getContractorCustomers(
        contractor.contractorId,
        { signal }
      );
      return customers.map(customer => ({
        ...customer,
        contractorId: contractor.contractorId
      }));
    })
    // Back to sequential processing
    .awaited()
    .filter(customer => customer.isActive)
    .sorted(createComparator({ sortBy, sortDirection }))
    .drop(page * pageSize)
    .take(pageSize)
    .withSignal(signal)
    .toArray();
}

Stream Processing with Error Handling

async function processLogFiles(files: string[]) {
  return Yielded.from(files)
    .parallel(3)
    .flatMap(async (filename) => {
      try {
        const content = await readFile(filename);
        return parseLogEntries(content);
      } catch (error) {
        console.error(`Error processing ${filename}:`, error);
        return [];
      }
    })
    .awaited()
    .filter(entry => entry.level === 'ERROR')
    .groupBy(entry => entry.timestamp.slice(0, 10)) // Group by date
    .toArray();
}

Infinite Sequences

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

// Take only what you need from infinite sequences
const firstTenFibs = Yielded.from(fibonacci())
  .take(10)
  .toArray();
// => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

TypeScript Support

Yielded is written in TypeScript and provides full type inference:

import { Yielded, type IYielded, type IAsyncYielded } from "@jenbuska/yielded";

// Types are automatically inferred
const numbers: IYielded<number> = Yielded.from([1, 2, 3]);
const strings = numbers.map(n => n.toString()); // IYielded<string>
const evens = numbers.filter((n): n is 2 | 4 => n % 2 === 0); // Type narrowing works!

// Async types
const asyncNumbers: IAsyncYielded<number> = Yielded.from(asyncGenerator());

Performance Considerations

  • Lazy evaluation means operations only execute when needed, saving CPU cycles
  • Memory efficient - processes one item at a time instead of creating intermediate arrays
  • Parallel processing - leverage concurrency for I/O-bound async operations
  • Native methods - uses native Iterator methods when available for better performance

When to Use Yielded

Good use cases:

  • Processing large datasets that don't fit in memory
  • Async data pipelines with multiple transformation steps
  • Stream processing with lazy evaluation
  • Parallel async operations with concurrency control

Consider alternatives:

  • Simple array operations where you need all data immediately (use native array methods)
  • Very small datasets where overhead isn't worth it
  • When you need random access to elements

Browser and Node.js Support

Node.js (CI-tested on all active LTS releases):

  • ✅ Node.js 20 (maintenance LTS, 20.4.0+ required for Symbol.dispose)
  • ✅ Node.js 22 (active LTS)
  • ✅ Node.js 24 (current)

Browsers (all tested with Playwright in CI):

  • ✅ Chrome 90+
  • ✅ Firefox 88+
  • ✅ Edge 90+
  • ✅ Safari — works out of the box (WebKit, no polyfills needed)

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

License

MIT © jEnbuska

Related Resources