Skip to content
Naftali Lubin

Need a website? Let’s talk

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 Observable

Let's build our own Observable

Meetup talk, YouTube

Watch on YouTube: Let's build our own Observable

What 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