Skip to content

Powerful iterator utilities for ES2022+ with statistical operations, windowing, and lazy evaluation. Forward compatible with ES2025 iterator helpers.

License

Notifications You must be signed in to change notification settings

mathscapes/iterflow

Repository files navigation

iterflow

Iterator utilities for ES2022+ with statistical operations, windowing, and lazy evaluation.

npm version License: Unlicense

Installation

npm install iterflow

Quick Start

import { iter } from 'iterflow';

// Statistical operations
iter([1, 2, 3, 4, 5]).mean();    // 3
iter([1, 2, 3, 4, 5]).median();  // 3

// Windowing
iter([1, 2, 3, 4, 5])
  .window(2)
  .toArray();
// [[1,2], [2,3], [3,4], [4,5]]

// Method chaining
iter([1, 2, 3, 4, 5, 6])
  .filter(x => x % 2 === 0)
  .map(x => x * 2)
  .chunk(2)
  .toArray();
// [[4, 8], [12]]

Resource Limits & Safety (v0.8.0)

New in v0.8.0: Production-ready safety features to protect against infinite loops, slow operations, and runaway resource usage:

// Prevent infinite loops with hard limits
iter.range(Infinity)
  .limit(10000)  // Throws if exceeded
  .toArray(1000); // Collects max 1000 items

// Timeout async operations
await asyncIter(items)
  .map(slowOperation)
  .timeout(5000)  // 5s per iteration
  .toArray();

// User cancellation with AbortController
const controller = new AbortController();
const promise = asyncIter(largeJob)
  .withSignal(controller.signal)
  .toArray();

// User clicks cancel
controller.abort('User cancelled');

New APIs:

  • limit(maxIterations) - Throw OperationError if iteration limit exceeded
  • timeout(ms) - Throw TimeoutError if async operations are too slow
  • withSignal(signal) - Integrate with AbortController for cancellation
  • toArray(maxSize?) - Optional size limit for safe collection
  • New error types: TimeoutError, AbortError

For complete details, see the Resource Limits Guide.

API

Wrapper API

import { iter } from 'iterflow';

iter([1, 2, 3, 4, 5])
  .filter(x => x > 2)    // Native iterator method
  .map(x => x * 2)       // Native iterator method
  .sum();                // iterflow extension - 24

Functional API

import { sum, filter, map } from 'iterflow/fn';

const data = [1, 2, 3, 4, 5];
sum(map(x => x * 2)(filter(x => x > 2)(data))); // 24

Operations

Statistical

iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).sum();          // 55
iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).mean();         // 5.5
iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).median();       // 5.5
iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).variance();     // 8.25
iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).percentile(75); // 7.75

Windowing

// Sliding window
iter([1, 2, 3, 4, 5]).window(3).toArray();
// [[1,2,3], [2,3,4], [3,4,5]]

// Non-overlapping chunks
iter([1, 2, 3, 4, 5]).chunk(2).toArray();
// [[1,2], [3,4], [5]]

// Consecutive pairs
iter([1, 2, 3, 4]).pairwise().toArray();
// [[1,2], [2,3], [3,4]]

Grouping

// Partition by predicate
const [evens, odds] = iter([1, 2, 3, 4, 5, 6])
  .partition(x => x % 2 === 0);

// Group by key function
const items = [
  { category: 'fruit', name: 'apple' },
  { category: 'vegetable', name: 'carrot' },
  { category: 'fruit', name: 'banana' }
];

const groups = iter(items).groupBy(item => item.category);

Set Operations

// Remove duplicates
iter([1, 2, 2, 3, 3, 3, 4]).distinct().toArray();
// [1, 2, 3, 4]

// Remove duplicates by key
iter(people).distinctBy(person => person.id).toArray();

Combining

// Zip iterators
iter.zip([1, 2, 3], ['a', 'b', 'c']).toArray();
// [[1,'a'], [2,'b'], [3,'c']]

// Interleave round-robin
iter.interleave([1, 2, 3], [4, 5, 6]).toArray();
// [1, 4, 2, 5, 3, 6]

// Merge sorted iterators
iter.merge([1, 3, 5], [2, 4, 6]).toArray();
// [1, 2, 3, 4, 5, 6]

Utilities

// Side effects
iter([1, 2, 3])
  .tap(x => console.log(`Processing: ${x}`))
  .map(x => x * 2)
  .toArray();

// Conditional take/drop
iter([1, 2, 3, 4, 3, 2, 1]).takeWhile(x => x < 4).toArray();
// [1, 2, 3]

iter([1, 2, 3, 4, 5]).dropWhile(x => x < 3).toArray();
// [3, 4, 5]

Generators

// Numeric ranges
iter.range(5).toArray();           // [0, 1, 2, 3, 4]
iter.range(2, 8).toArray();        // [2, 3, 4, 5, 6, 7]
iter.range(0, 10, 2).toArray();    // [0, 2, 4, 6, 8]

// Repeat values
iter.repeat('hello', 3).toArray();  // ['hello', 'hello', 'hello']
iter.repeat(0).take(5).toArray();   // [0, 0, 0, 0, 0]

Examples

Processing Pipeline

interface Sale {
  product: string;
  amount: number;
  category: string;
}

const sales: Sale[] = [
  { product: 'Laptop', amount: 1200, category: 'Electronics' },
  { product: 'Mouse', amount: 25, category: 'Electronics' },
  { product: 'Book', amount: 15, category: 'Books' },
];

// Average electronics sale amount
const electronicsAvg = iter(sales)
  .filter(sale => sale.category === 'Electronics')
  .map(sale => sale.amount)
  .mean();

// Total sales by category
const salesByCategory = iter(sales)
  .groupBy(sale => sale.category)
  .entries()
  .map(([category, sales]) => ({
    category,
    total: iter(sales).map(sale => sale.amount).sum()
  }));

Infinite Sequences

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

// First 10 even fibonacci numbers
const evenFibs = iter(fibonacci())
  .filter(x => x % 2 === 0)
  .take(10)
  .toArray();

Moving Averages

const temperatures = [20, 22, 25, 23, 21, 19, 18, 20, 22, 24];

const movingAverages = iter(temperatures)
  .window(3)
  .map(window => iter(window).mean())
  .toArray();

When to Use iterflow

iterflow balances developer experience with performance. Here's how to decide:

Use iterflow when:

  • Large datasets (1000+ items) - lazy evaluation avoids unnecessary work
  • Early termination - finding first match, taking limited results (find, some, every, take)
  • Memory efficiency - windowing, chunking, or processing huge files
  • Complex pipelines - chaining 3+ operations together
  • Statistical operations - mean, median, variance, percentile calculations
  • Code readability - method chaining feels more natural than manual loops

Consider alternatives when:

  • Small arrays (< 100 items) - native Array methods are slightly faster
  • Single simple operation - map or filter alone on small data
  • Performance-critical hot paths - called millions of times, every microsecond matters
  • Need multiple iterations - arrays are easier to loop over multiple times

The Trade-off

iterflow uses lazy evaluation, which means:

  • Lower memory usage - no intermediate arrays created
  • Slightly slower per operation - small function call overhead
  • Better for large datasets - memory savings far exceed speed cost
  • Better for partial consumption - stops early when possible

Real Performance Impact

For datasets < 100 items, the differences are negligible—use what feels natural.

For datasets > 1000 items:

  • With early termination (take 10 from 100K): iterflow can be 20-300x faster
  • With windowing (moving average): iterflow can be 35-600x faster due to memory efficiency
  • Full consumption (process all items): native arrays are 2-5x faster

See docs/BENCHMARKS.md for detailed performance data and specific operation comparisons.

Documentation

Contributing

We welcome contributions! Please see our PLAYBOOK.md for:

  • Development workflow and branching strategy
  • Commit message guidelines
  • Testing requirements
  • Release process

For quick start:

  1. Fork the repository
  2. Create a feature branch from dev
  3. Make your changes with tests
  4. Submit a PR to dev

See PLAYBOOK.md for complete details.

License

The Unlicense - See LICENSE for details.

About

Powerful iterator utilities for ES2022+ with statistical operations, windowing, and lazy evaluation. Forward compatible with ES2025 iterator helpers.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •