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

rx4u

v0.1.7

Published

Reactive data flow processing library

Readme

rx4u

rx4u is a small reactive stream library for JavaScript and TypeScript.

It is inspired by RxJS, but it uses plain functions instead of Observable classes. A stream is just a function. Operators are also functions. You compose them with pipeFrom.

Features

  • 🚀 Pure Functional Design - Avoids object-oriented patterns, embraces functional programming paradigms
  • Lazy Execution - Operators execute only when subscribed
  • 🔄 Independent Subscriptions - Each subscription is isolated by default
  • 🤝 Shared Operators - Use share operator to let multiple subscribers share the same source
  • 🧩 Rich Operator Set - 20+ common operators including transformation, combination, utility, and error handling
  • 📦 TypeScript Support - Complete type definitions and type inference
  • 🏗️ Modular Architecture - Independent package structure with tree-shaking support
  • Lightweight - Simplified stream implementation with high performance
  • 🧪 Comprehensive Testing - 219 test cases with 100% pass rate

Installation

npm install rx4u
pnpm add rx4u
yarn add rx4u

Quick Start

import {of, pipeFrom, filter, map, take} from 'rx4u';

const numbers$ = of(1, 2, 3, 4, 5, 6);

const result$ = pipeFrom(
  numbers$,
  filter((value) => value % 2 === 0),
  map((value) => value * 10),
  take(2)
);

const unsubscribe = result$(
  (value) => console.log(value),
  (error) => console.error(error),
  () => console.log('done')
);

unsubscribe();

Output:

20
40
done

Core Types

Sub<T>

A Sub<T> is a stream. It receives next, error, and complete callbacks. It returns an unsubscribe function.

type Sub<T> = (next?: (value: T) => void, error?: (error: unknown) => void, complete?: () => void) => () => void;

Operator<T, R>

An Operator<T, R> converts a stream of T values into a stream of R values.

type Operator<T, R> = (source: Sub<T>) => Sub<R>;

Core Helpers

pipeFrom(source, ...operators)

pipeFrom applies operators to a source stream from left to right.

Use it when you want readable stream pipelines.

import {of, pipeFrom, map, filter} from 'rx4u';

const result$ = pipeFrom(
  of(1, 2, 3),
  filter((value) => value > 1),
  map((value) => value * 2)
);

firstValueFrom(source)

firstValueFrom turns a stream into a Promise. It resolves with the first emitted value.

It rejects if the stream errors, or if the stream completes without any value.

import {firstValueFrom, of} from 'rx4u';

const value = await firstValueFrom(of(42));

Creation Functions

from(create)

Creates a stream from a synchronous factory function. The factory runs once for each subscription.

import {from} from 'rx4u';

const random$ = from(() => Math.random());

of(...values)

Creates a stream from values. It emits each value, then completes.

import {of} from 'rx4u';

const numbers$ = of(1, 2, 3);

interval(period)

Creates a stream that emits 0, 1, 2, ... every period milliseconds.

import {interval, take, pipeFrom} from 'rx4u';

const ticks$ = pipeFrom(interval(1000), take(3));

fromPromise(create)

Creates a stream from a Promise factory. The factory runs when you subscribe.

import {fromPromise} from 'rx4u';

const user$ = fromPromise(() => fetch('/api/user').then((res) => res.json()));

fromEvent(target, type, options?)

Creates a stream from DOM-like events.

import {fromEvent} from 'rx4u';

const clicks$ = fromEvent<MouseEvent>(button, 'click');

createState(initialValue)

Creates reactive state with an initial value.

It returns [state$, setState, getState, destroy].

import {createState} from 'rx4u';

const [count$, setCount, getCount, destroy] = createState(0);

count$((value) => console.log(value));
setCount((value) => value + 1);
console.log(getCount());
destroy();

createLazyState<T>()

Creates reactive state without an initial value.

It returns [state$, setState, getState, destroy]. Subscribers only receive a value after setState is called.

import {createLazyState} from 'rx4u';

const [name$, setName] = createLazyState<string>();

name$((value) => console.log(value));
setName('Ada');

createSubject<T>()

Creates a simple multicast stream.

It returns [subject$, next]. Calling next(value) sends the value to all current subscribers.

import {createSubject} from 'rx4u';

const [message$, nextMessage] = createSubject<string>();

message$((value) => console.log(value));
nextMessage('hello');

Operators

map(mapFn)

Transforms each value.

pipeFrom(
  of(1, 2),
  map((value) => value * 2)
);

filter(predicate)

Only emits values that pass the predicate.

pipeFrom(
  of(1, 2, 3),
  filter((value) => value > 1)
);

share()

Shares one source subscription between many subscribers.

const shared$ = pipeFrom(fromPromise(loadData), share());

merge(...sources)

Combines many streams into one stream by forwarding values from all sources.

const all$ = merge(of(1, 2), of(3, 4));

combineLatest(...sources)

Emits the latest values from all sources after every source has emitted at least once.

const combined$ = combineLatest(of(1, 2), of('a', 'b'));

zip(...sources)

Pairs values from multiple sources by order.

const pairs$ = zip(of(1, 2), of('a', 'b'));

withLatestFrom(...others)

Combines each source value with the latest values from other streams.

const result$ = pipeFrom(clicks$, withLatestFrom(state$));

take(count)

Emits only the first count values, then completes.

pipeFrom(interval(1000), take(3));

takeWhile(predicate, inclusive?)

Emits values while the predicate returns true.

If inclusive is true, it also emits the first value that fails the predicate.

pipeFrom(
  of(1, 2, 3),
  takeWhile((value) => value < 3)
);

takeUntil(notifier)

Emits source values until the notifier stream emits.

pipeFrom(source$, takeUntil(stop$));

skip(count)

Skips the first count values.

pipeFrom(of(1, 2, 3), skip(1));

delay(ms)

Delays each value by ms milliseconds.

pipeFrom(of('ready'), delay(500));

debounceTime(delay)

Waits for a quiet period, then emits the latest value.

This is useful for search input.

pipeFrom(input$, debounceTime(300));

distinctUntilChanged(compareFn?, keySelector?)

Skips a value when it is the same as the previous value.

You can pass a custom compare function or a key selector.

pipeFrom(of(1, 1, 2), distinctUntilChanged());

tap(tapFn)

Runs a side effect for each value without changing the value.

pipeFrom(
  source$,
  tap((value) => console.log(value))
);

timeout(ms, errorFactory?)

Errors if the source does not emit within ms milliseconds.

pipeFrom(request$, timeout(5000));

switchMap(project)

Maps each value to an inner stream or Promise and keeps only the latest one.

This is useful for search requests where old requests should be ignored.

pipeFrom(
  query$,
  switchMap((query) => fromPromise(() => search(query)))
);

mergeMap(project, concurrent?)

Maps each value to an inner stream and merges the results.

Use concurrent to limit how many inner streams run at the same time.

pipeFrom(
  ids$,
  mergeMap((id) => fromPromise(() => loadUser(id)), 4)
);

concatMap(project)

Maps each value to an inner stream and runs them one at a time, in order.

pipeFrom(
  tasks$,
  concatMap((task) => fromPromise(() => runTask(task)))
);

catchError(selector)

Handles an error by switching to a fallback stream.

pipeFrom(
  risky$,
  catchError(() => of('fallback'))
);

retry(count?)

Retries the source when it errors. The default count is 3.

pipeFrom(request$, retry(2));

retryWhen(notifier)

Retries when the notifier stream emits.

Use it for custom retry timing or retry rules.

pipeFrom(
  request$,
  retryWhen((errors$) => pipeFrom(errors$, delay(1000), take(3)))
);

startWith(...values)

Emits values before the source starts.

pipeFrom(data$, startWith('loading'));

endWith(...values)

Emits values after the source completes.

pipeFrom(data$, endWith('done'));

throttleTime(duration, options?)

Limits how often values are emitted.

By default it emits the first value in each time window. You can also enable trailing values.

pipeFrom(scroll$, throttleTime(1000, {leading: true, trailing: true}));

scan(accumulator, seed?)

Keeps running state and emits each intermediate result.

pipeFrom(
  of(1, 2, 3),
  scan((sum, value) => sum + value, 0)
);

reduce(accumulator, seed?)

Keeps running state and emits only the final result when the source completes.

pipeFrom(
  of(1, 2, 3),
  reduce((sum, value) => sum + value, 0)
);

License

MIT License

🤝 Contributing

Issues and Pull Requests are welcome!

📞 Support

If you encounter any problems during usage, please:

  1. Check the documentation and examples
  2. Search existing Issues
  3. Create a new Issue describing the problem

rx4u - Making reactive programming simpler! 🚀