IxJS

LINQ-style Array#extras operators for composing lazy iterables and async iterables in JavaScript and TypeScript.

Library
npm
v7.0.0
1,411stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
49/100Fair
Development Activity0
Maintenance32
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
74/100Good
Architecture80
Code Quality78
Innovation68
Learning Curve70

IxJS (published to npm as ix) is the Interactive Extensions for JavaScript, a sibling project to RxJS from the ReactiveX organization. Where RxJS unifies push-based, event-driven collections behind Observable, IxJS unifies pull-based collections behind two core abstractions — Iterable for synchronous sequences and AsyncIterable for asynchronous ones — and layers Array#extras-style operators (map, filter, reduce, take, groupBy, and dozens more) on top of both.

The library is built around the native ES2015 Symbol.iterator and Symbol.asyncIterator protocols, so anything already iterable (arrays, generators, async generators, Node streams, DOM events) can be wrapped with IterableX.as or AsyncIterableX.as and immediately composed with .pipe(). Operators are also exposed as free functions, and consumers can opt into either a tree-shakeable functional style (import { map } from 'ix/iterable/operators') or a fluent, method-chaining style via the ix/add/... prototype-augmentation entry points, trading bundle size for ergonomics as needed.

Because consumption is pull-based rather than push-based, IxJS is well suited to I/O-bound work — reading paginated APIs, streaming files, or processing async generators — where the consumer, not the producer, controls the pace of iteration. The project is TypeScript-first, ships full type definitions, and builds to ES5/ES2015/ESNext and CommonJS/UMD targets to support a wide range of consumers.

What You Get

  • IterableX / AsyncIterableX base classes - abstract classes implementing [Symbol.iterator]/[Symbol.asyncIterator] that every operator and factory builds on, exposing .pipe() for functional composition and .forEach() for direct consumption.
  • A large operator library - map, filter, reduce, take, skip, groupBy, flatMap, zip, merge, catchError, retry, debounce, buffer, and more, mirrored across both the sync and async namespaces (ix/iterable/operators and ix/asynciterable/operators).
  • Creation factories - of, from, range, generate, defer, empty, and iif for building new synchronous or asynchronous sequences from scratch.
  • Two consumption styles - a tree-shakeable functional/pipeable API for minimal bundle size, or a fluent prototype-augmentation API (ix/add/...) for method-chaining ergonomics.
  • Multi-target builds - compiled output for ES5, ES2015, and ESNext, plus CommonJS and UMD bundles, so the library can be consumed from legacy and modern environments alike.
  • First-class TypeScript types - the entire library is authored in TypeScript with generics on every operator, giving full type inference through a .pipe() chain.

Common Use Cases

  • Paginated API consumption - wrap an async generator that fetches pages on demand and compose map/filter/take over it without loading every page into memory up front.
  • Stream and file processing - treat a Node.js readable stream or async generator as an AsyncIterable and apply query operators instead of hand-rolling buffering logic.
  • In-memory collection queries - use the Iterable operators as a lazy, LINQ-style alternative to chained Array.prototype calls when working with large or infinite synchronous sequences.
  • Backpressure-friendly pipelines - because iteration is pull-based, a slow consumer naturally throttles a fast producer without extra coordination code.

Under The Hood

Architecture The library is organized around two parallel abstract base classes, IterableX (src/iterable/iterablex.ts) and AsyncIterableX (src/asynciterable/asynciterablex.ts), each implementing the corresponding iteration protocol and exposing a static .as() adapter plus an instance .pipe() method for functional composition. Every operator (src/iterable/operators/.ts and src/asynciterable/operators/.ts) follows the same shape: a small private class extending the base (e.g. MapIterable in map.ts) that implements the iterator via a generator method, paired with an exported factory function returning an OperatorFunction closure consumed by .pipe(). This keeps each operator self-contained and composable without a central dispatcher, so adding a new operator never touches existing code — the tradeoff is real duplication of the constructor/iterator boilerplate across the ~50+ operator files in each namespace.

Tech Stack Authored entirely in TypeScript targeting ESNext with isolated modules, using native Symbol.iterator/Symbol.asyncIterator rather than a polyfill layer (with abortcontroller-polyfill and @types/node as the only informational runtime dependencies, plus tslib for helper inlining). The build pipeline is Gulp-orchestrated (gulpfile.js) driving esbuild and the TypeScript compiler to emit multiple targets (ES5, ES2015, ESNext) in both ESM and CommonJS/UMD module formats, with Rollup plugins used for bundle assembly and Google Closure Compiler available for minification checks.

Code Quality Tests live under spec/ as one *-spec.ts file per operator (e.g. spec/iterable/as-spec.ts), run through Jest with ts-jest and custom helper modules (iterablehelpers.ts, asynciterablehelpers.ts) providing shared assertions like hasNext/noNext for iterator-protocol-level checks — this gives thorough per-operator coverage rather than integration-style tests. ESLint with the TypeScript and Jest plugins runs in CI (lint.yml, test.yml, main.pr.yml), and the codebase is fully typed with no any escape hatches visible in the core operator implementations, though error handling in operators is minimal by design since exceptions are expected to propagate through the iterator protocol naturally.

What Makes It Unique IxJS’s distinguishing choice is treating pull-based iteration as a first-class citizen alongside RxJS’s push-based Observable, from the same organization and with a deliberately parallel API shape — so teams already fluent in RxJS operators can apply the same mental model to synchronous and asynchronous iterables. Supporting both Iterable and AsyncIterable as fully separate but API-mirrored namespaces, with dual consumption styles (functional pipe vs. fluent prototype augmentation) for bundle-size tradeoffs, is a level of dual-protocol, dual-ergonomics support that most single-purpose utility libraries don’t attempt.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search