Project · 2017–2022
Servable
An Observable library written from scratch.
- Language
- JavaScript (ES modules)
- Dependencies
- None at runtime
- Published
- npm: servable (2017), @maniator/servable 1.0.0 (2021)
The talks
Two meetup talks walk through building an Observable from nothing to a working stream with operators, the same way I built the library.
Let's build our own Observable from nothing to a simple Observable
Meetup talk, YouTube
Watch on YouTube: Let's build our own Observable from nothing to a simple ObservableLet's build our own Observable
Meetup talk, YouTube
Watch on YouTube: Let's build our own ObservableWhat it is
A small, dependency-free implementation of Observables: the Observable, Observer, Subscription and Subject types, 22 operators (map, filter, take, takeUntil, debounceTime, switchMap, merge, zip, scan and more) and helpers that create streams from events, promises, intervals, ranges and AJAX calls.
What I built
All of it, without AI. I read the RxJS docs and built each piece to do what they described. I wrote the library in 2017, shipped a run of 0.x releases in its first three weeks, then came back in 2018 to add Jest tests, API docs and a cancellable AJAX source, and moved the build from Bublé to Babel. In 2021 it moved to the scoped @maniator/servable package with a 1.0.0 release published from CI.
Tech stack
JavaScript ES modules, bundled with Rollup and Babel, minified with terser, tested with Jest and documented with JSDoc.
Two engineering decisions
Operators that work with or without chaining
Each operator is a standalone function and is also attached to the prototype, so you can call map(source$, fn) or chain source$.map(fn).
src/operators/map.js
export const map = function (source$, mapCallback) {
return passThroughNext(source$, function ({ next }, value) {
next(mapCallback(value));
});
};
Observable.map = map;
Observable.prototype.map = function (mapCallback) {
return map(this, mapCallback);
};One place for teardown
Most operators are built on a single helper. Whatever the operator does with each value, the function it returns disposes of the operator’s own resources and unsubscribes from the source, so a chain cleans up all the way down when you unsubscribe at the end.
src/operators/passThroughNext.js
export const passThroughNext = function (source$, nextFunction, dispose = noop) {
return new Observable((observer) => {
const subscription = source$.subscribe({
next: nextFunction.bind(this, observer),
error: observer.error,
complete: observer.complete,
});
return function () {
dispose();
subscription.unsubscribe();
};
});
};Usage
Snapshot. npm download counts, pulled Oct 14, 2026. They include automated installs from CI and registry mirrors, so one person can account for many of them.
- servable
- 13,217 downloads all time; 1,682 in the last 12 months
- @maniator/servable
- 526 downloads all time; 141 in the last 12 months