<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://fbiville.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://fbiville.github.io/" rel="alternate" type="text/html" /><updated>2022-12-22T16:11:50+00:00</updated><id>https://fbiville.github.io/feed.xml</id><title type="html">🎸 Florent + The Machine</title><subtitle>Insatiable Learner</subtitle><author><name>Florent Biville</name></author><entry><title type="html">Node.js Streams For Fun And Profit</title><link href="https://fbiville.github.io/2020/04/16/Node_Streams_For_Fun_And_Profit.html" rel="alternate" type="text/html" title="Node.js Streams For Fun And Profit" /><published>2020-04-16T00:00:00+00:00</published><updated>2020-04-16T00:00:00+00:00</updated><id>https://fbiville.github.io/2020/04/16/Node_Streams_For_Fun_And_Profit</id><content type="html" xml:base="https://fbiville.github.io/2020/04/16/Node_Streams_For_Fun_And_Profit.html">&lt;p&gt;I joined the &lt;a href=&quot;https://projectriff.io&quot;&gt;riff&lt;/a&gt; team at Pivotal a year and a half ago.
I have been working for more than a year on &lt;a href=&quot;https://projectriff.io&quot;&gt;riff&lt;/a&gt; invokers.&lt;/p&gt;

&lt;p&gt;This probably deserves a blog post on its own, but invokers, in short, have the responsibility of invoking user-defined functions
and exposing a way to send inputs and receive outputs.
The &lt;a href=&quot;https://github.com/projectriff/invoker-specification/&quot;&gt;riff invocation protocol&lt;/a&gt; formally defines the scope of such invokers.&lt;/p&gt;

&lt;p&gt;Part of my job has been to update the existing invokers (especially the &lt;a href=&quot;https://github.com/projectriff/node-function-invoker&quot;&gt;Node.js one&lt;/a&gt;) so that they comply with this spec.
As the invocation protocol is a &lt;a href=&quot;https://github.com/projectriff/invoker-specification/blob/a41d885fb411dc00e7ea3f7724ede4c435121a62/riff-rpc.proto#L13&quot;&gt;streaming-first protocol&lt;/a&gt;,
I had to really brush up my knowledge about Node.js streams (narrator’s voice: well, learn from zero).&lt;/p&gt;

&lt;p&gt;I learnt a lot by trial and error, probably more than I care to admit.
This blog post serves as an introduction to Node.js streams.
Hopefully, it also outlines some good practices, and some annoying pitfalls to avoid.&lt;/p&gt;

&lt;h2 id=&quot;thanks-dear-proofreaders&quot;&gt;Thanks, Dear (Proof)Readers&lt;/h2&gt;

&lt;p&gt;I would like to thank:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/old_sound&quot;&gt;Alvaro Videla&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/nicokosi&quot;&gt;Nicolas Kosinski&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/poledesfetes&quot;&gt;Vladimir de Turckheim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for the various suggestions to make this better. Thanks ❤️&lt;/p&gt;

&lt;h2 id=&quot;harder-better-mapper-zipper&quot;&gt;Harder, Better, Mapper, Zipper&lt;/h2&gt;

&lt;p&gt;Let’s create a tiny Node.js library that works with streams and provide
familiar functional operators such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, what is a stream?&lt;/p&gt;

&lt;p&gt;Loosely defined, a stream conveys (possibly indefinitely) chunks of data, to which specific operations can be applied.&lt;/p&gt;

&lt;p&gt;How does that translate to Node.js exactly?&lt;/p&gt;

&lt;h2 id=&quot;streams-in-nodejs&quot;&gt;Streams in Node.js&lt;/h2&gt;

&lt;p&gt;Node.js streams come in two flavors: &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_streams&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_writable_streams&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; streams can be read from&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; streams can be written to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#pipe&lt;/code&gt;&lt;/a&gt; allows to create a pipeline, where the inputs come from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; stream and are written
to the destination &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; stream.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Writable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Readable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Writable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What happens here is that the source &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; stream goes from a paused state to a &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_three_states&quot;&gt;flowing state&lt;/a&gt; after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; is called.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can manually manage such state transitions with functions like &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_pause&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#pause&lt;/code&gt;&lt;/a&gt;
or &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_resume&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#resume&lt;/code&gt;&lt;/a&gt; but we are only going to rely on automatic flowing mode from now on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Node.js stream can also encapsulate a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side &lt;strong&gt;and&lt;/strong&gt; a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; side, such streams are called &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_class_stream_duplex&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt;&lt;/a&gt; streams.
If outputs of the duplex stream depend on inputs, then a &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_class_stream_transform&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;&lt;/a&gt; stream is the way to go (it is a specialization of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt; type).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Outputs are &lt;em&gt;read&lt;/em&gt;, hence they come from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt; stream.&lt;/p&gt;

  &lt;p&gt;Inputs are &lt;em&gt;written&lt;/em&gt;, hence they go to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; side of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt; stream.&lt;/p&gt;

  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; streams automatically expose chunks from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; side to a user-defined transformation function.
The function results are automatically forwarded to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; stream.&lt;/p&gt;

  &lt;p&gt;Note: unfortunately, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt; streams do not differentiate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; errors from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/node_streams.svg&quot; alt=&quot;Node.js stream family&quot; title=&quot;Node.js stream family diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These compound streams are interesting for any kind of pipeline beyond basic ones.
They encode intermediate transformations before chunks reach the final destination &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; stream.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Writable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Readable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream1&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream2&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream3&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Writable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myTransformStream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myTransformStream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myTransformStream3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above “fluent” example works because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#pipe&lt;/code&gt; returns the reference to the destination stream.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; (or more generally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt;) streams have two sides, so they can be piped to (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; side) and then from (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side) via a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;However, this is not necessarily the best way to define a &lt;strong&gt;linear&lt;/strong&gt; pipeline though.
One important limitation is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; does not offer any particular assistance when it comes to error handling.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Emphasis on linear here. Streams can be piped from and to several times, so you can end up with graph-shaped pipelines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A more robust alternative in case of linear pipelines is to use the built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; function.
It must be called with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; stream (a.k.a. the source)&lt;/li&gt;
  &lt;li&gt;0..n &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Duplex&lt;/code&gt; stream (a.k.a. intermediates)&lt;/li&gt;
  &lt;li&gt;1 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; stream (a.k.a. the destination)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Writable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Readable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream1&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream2&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myTransformStream3&lt;/span&gt;  &lt;span class=&quot;cm&quot;&gt;/* = instantiate Transform stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* = instantiate Writable stream */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myReadableStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myTransformStream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myTransformStream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myTransformStream3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myWritableStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also provide a callback that will be invoked when the pipeline completes, abnormally (i.e. when an error occurs) or not.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; invokes the completion callback even if any of the streams’ setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoDestroy&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; actually supports more than streams but that’s out of scope for this article.
Feel free to check &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback&quot;&gt;the documentation&lt;/a&gt; to learn about other usages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that the general pipeline model is understood, let’s dive into the details of how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; works, learning how custom streams are implemented in the process.&lt;/p&gt;

&lt;h2 id=&quot;you-cant-map-this&quot;&gt;You Can’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; This&lt;/h2&gt;

&lt;p&gt;Credit where credit is due, I am going to reuse the awesome diagrams of &lt;a href=&quot;https://projectreactor.io/&quot;&gt;project Reactor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/mapForFlux.svg&quot; alt=&quot;`map` diagram&quot; title=&quot;`map` diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The top of the diagram depicts chunks as they initially come to the stream, as well as
the stream completion signal (marked by the bold vertical line at the end of the sequence).&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; operation here is in the middle, applying a transformation from circles to squares.&lt;/p&gt;

&lt;p&gt;The bottom part of the diagram shows the resulting chunks and how the completion signal is propagated as-is.&lt;/p&gt;

&lt;p&gt;In other terms, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; applies a transformation function to each element of the stream, in the order they arrive.&lt;/p&gt;

&lt;p&gt;Let’s start with a &lt;a href=&quot;https://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; test:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;map operator =&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 
    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;applies transformations to chunks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transformation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MapTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// ??? (4)&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;transformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (5)&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeFalsy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pipeline should successfully complete&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few things of note:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You can create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; from an iterable source such as an array, or a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*&quot;&gt;generator function&lt;/a&gt;. Here, the stream will emit each array element in succession.
The &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_object_mode&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;objectMode&lt;/code&gt;&lt;/a&gt; option configures the stream to receive any kind of chunk.
The default chunk data type is textual or binary (i.e. strings, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Buffer&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Uint8Array&lt;/code&gt;).
Quite surprisingly, the default mode when specifically using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#from&lt;/code&gt; is the object mode, contrary to stream constructors. However redundant, the object mode is set here just for consistency’s sake.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MapTransform&lt;/code&gt; does not exist yet, we will have to figure out its implementation next but we can assume its constructor accepts a transformation function (here: the square function).
We could pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;objectMode&lt;/code&gt; setting, but let’s assume it always operates this way.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/stream.html#stream_class_stream_passthrough&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassThrough&lt;/code&gt;&lt;/a&gt; is a special implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; stream which directly forwards inputs as outputs (it applies the identity function in other words).&lt;/li&gt;
  &lt;li&gt;we need to somehow accumulate the observed outputs to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;, more on that soon&lt;/li&gt;
  &lt;li&gt;we leverage the completion callback of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; to verify a few things:
    &lt;ol&gt;
      &lt;li&gt;the pipeline completes successfully&lt;/li&gt;
      &lt;li&gt;the observed results are consistent with the transformation we intend to apply on the initial chunks&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;done&lt;/code&gt; is a Jasmine utility to notify the test runner of the (asynchronous) test completion&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For people familiar with the given-when-then test structure, this test may look a bit strange.
Indeed, the order is changed here to given-then-when. This has to do with the asynchronous nature of streams.
We have to set up the expectations (the “then” block) before data starts flowing in, i.e. before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; is called.&lt;/p&gt;

&lt;p&gt;How can we be sure the test completes? After all, streams can be infinite.
In that case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#from&lt;/code&gt; reads a finite array and will send a completion signal once the array is fully consumed.
This completion signal will be forwarded to all the other (downstream) streams, we can therefore be confident the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; completion callback is going to be called.
In the worst case, the test will hang for a while until the Jasmine timeout is reached, causing a test failure.&lt;/p&gt;

&lt;p&gt;We now need to figure out how to complete the test.&lt;/p&gt;

&lt;p&gt;Node.js streams extend &lt;a href=&quot;https://nodejs.org/api/events.html#events_events&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventEmitter&lt;/code&gt;&lt;/a&gt;.
They emit specific events that can be listened to via functions such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventEmitter#on(eventType, callback)&lt;/code&gt;.
Event listeners are &lt;strong&gt;synchronously&lt;/strong&gt; executed in the order they are added (you can tweak the order via alternative functions such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventEmitter#prependListener(eventType, callback)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Our test needs to observe chunks written to the destination stream.
Technically, the destination could just be a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; stream as this is the only requirement of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt;.
However, we need to read the chunks that have been written to, so using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; stream such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassThrough&lt;/code&gt; definitely helps as it exposes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side.&lt;/p&gt;

&lt;p&gt;In particular, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; streams emit a &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_event_data&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; event&lt;/a&gt; with the associated chunk of data. That is exactly what we need to
accumulate the results!&lt;/p&gt;

&lt;p&gt;Our test now becomes:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;map operator =&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 
    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;applies transformations to chunks&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transformation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MapTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;transformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeFalsy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pipeline should successfully complete&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The test seems ready. If I execute it, I get:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test
&lt;/span&gt;Failures:
1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; map operator &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; applies transformations to chunks
  Message:
    ReferenceError: MapTransform is not defined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just to make sure the pipeline is properly set up, let’s temporarily replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MapTransform&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassThrough&lt;/code&gt; in object mode.
In that case, the test should fail because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt; will be equal to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, 2, 3]&lt;/code&gt; and not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, 4, 9]&lt;/code&gt;.
Let’s see:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test
&lt;/span&gt;1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; map operator &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; applies transformations to chunks
  Message:
    Expected &lt;span class=&quot;nv&quot;&gt;$[&lt;/span&gt;1] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 2 to equal 4.
    Expected &lt;span class=&quot;nv&quot;&gt;$[&lt;/span&gt;2] &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 3 to equal 9.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The test fails as expected, let’s focus on the implementation now.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; is an intermediate transformation, directly correlating outputs to inputs.
Hence, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; is the ideal choice.&lt;/p&gt;

&lt;p&gt;Let’s subclass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;, then:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MapTransform&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ???&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt; streams need to implement the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_transform&lt;/code&gt; method&lt;/a&gt;.
The first parameter is the chunk of data coming to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writable&lt;/code&gt; side, the second is the encoding (which is irrelevant in object mode) and the third one is a callback
that must be called &lt;strong&gt;exactly once&lt;/strong&gt; to notify either an error or null (first argument) or pass on the result to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side (second argument).&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MapTransform&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Transform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s see if the test passes now:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; jasmine

Randomized with seed 30817
Started
&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;


1 spec, 0 failures
Finished &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;0.014 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;🍾 It does!&lt;/p&gt;

&lt;p&gt;We could improve a few things, such as accepting asynchronous functions and handling throwing functions.
This is left as an exercise to the readers 😉 (hint: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Promise.resolve&lt;/code&gt; bridges synchronous and asynchronous functions)&lt;/p&gt;

&lt;h2 id=&quot;zip-it&quot;&gt;Zip it!&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; is slightly more complex than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; as it operates on (at least) two streams.
Let’s see it in action (thanks again to &lt;a href=&quot;https://projectreactor.io/&quot;&gt;project Reactor&lt;/a&gt; for the diagrams):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/zip.svg&quot; alt=&quot;`zip` diagram&quot; title=&quot;`zip` diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; pairs up chunks by order of arrival.
Once the pair is formed, a transformation function is applied to it.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; completes when the last stream completes.&lt;/p&gt;

&lt;p&gt;For simplicity’s sake, our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; implementation will only pair elements together but not apply any transformation.&lt;/p&gt;

&lt;p&gt;Time to express our intent with a test:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;zip operator =&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pairs chunks from upstream streams&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upstream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upstream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Un&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Deux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Trois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;zipSource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;upstream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upstream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (4)&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (4)&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;zipSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (5)&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeFalsy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pipeline should successfully complete&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Un&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Deux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Trois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is very similar to the previous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; test:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;we need two streams to read from, hence the creation of two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; streams from different arrays.
Note we could (and should for a production implementation) spice up the test a bit by introducing latency, thus making sure we properly wait for chunks to be paired in order.
This could be done with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*&quot;&gt;generator functions&lt;/a&gt;
and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setTimeout&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;the next step will be to figure out how to implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipReadable&lt;/code&gt;. We can safely assume it accepts two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; streams to read chunks from.&lt;/li&gt;
  &lt;li&gt;same as before, we rely on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassThrough&lt;/code&gt; to receive the resulting chunks. We will use its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side to observe and accumulate the results.&lt;/li&gt;
  &lt;li&gt;we accumulate the observed resulting chunks in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;, based on the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_event_data&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; event&lt;/a&gt; emitted by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; side of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PassThrough&lt;/code&gt; stream&lt;/li&gt;
  &lt;li&gt;finally, we rely on the completion callback to make sure, as before, that the pipeline successfully completes, the resulting chunks are as we expect and notify Jasmine of the test completion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s run the test:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test
&lt;/span&gt;Failures:
1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; zip operator &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; pairs chunks from upstream streams
  Message:
    ReferenceError: ZipReadable is not defined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s create an implementation that works with two streams for now.
First, what kind of stream our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipReadable&lt;/code&gt; should be? Let’s go with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt;, as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipReadable&lt;/code&gt; acts as a source
built upon two upstream streams.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// ??? (2)&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ??? (1)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ??? (1)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;we need to get data from both the upstream streams. We chose here not to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_startReading&lt;/code&gt; in the constructor.
The goal is to start reading only when a first consumer wants to read data.&lt;/li&gt;
  &lt;li&gt;we somehow need to emit data whenever &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipReadable&lt;/code&gt; is read from&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s first worry about buffering the incoming data:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ???&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nothing too fancy here, chunks are pushed to the corresponding array.
Custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; need to implement &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_read_size_1&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#_read&lt;/code&gt;&lt;/a&gt;.
Results are pushed to consumers via &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_push_chunk_encoding&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#push&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s have a crack at it:&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// DO NOT USE IN PRODUCTION - SEE BELOW FOR DETAILS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (4)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (5)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;upon the first call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#_read&lt;/code&gt; (when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; is called in the test), we start reading data from the upstream sources.
As we do not want to subscribe to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'data'&lt;/code&gt; event multiple times, we guard this initialization with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.initialized&lt;/code&gt; flag.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; is advisory, so we could just ignore it but it does not cost much to include in the bound computation. More on that towards the end of this article.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;splice&lt;/code&gt; is used here to remove and return the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bound&lt;/code&gt; first elements of each array as well as shift the remaining ones. That way, we do not keep consumed chunks around.&lt;/li&gt;
  &lt;li&gt;the core logic of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; is here, we create a pair (an array) of chunks accumulated from two streams&lt;/li&gt;
  &lt;li&gt;finally, we publish that pair&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s see if our test is happy:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Failures:
1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; zip operator &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; pairs chunks from upstream streams
  Message:
    Error: Timeout - Async &lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;did not &lt;span class=&quot;nb&quot;&gt;complete &lt;/span&gt;within 5000ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;by jasmine.DEFAULT_TIMEOUT_INTERVAL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oh no! The test fails.
Looking at the above implementation, this actually makes sense.
When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_read&lt;/code&gt; is called the first time, there is no guarantee at all that data has been buffered yet from the upstream sources.&lt;/p&gt;

&lt;p&gt;Looking a bit more closely to &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_read_size_1&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#_read&lt;/code&gt; documentation&lt;/a&gt;, we can read:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Once the readable._read() method has been called, it will not be called again until more data is pushed through the readable.push() method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ahah! That’s exactly the issue we hit! &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_read&lt;/code&gt; is called a first time when the pipeline is set up, but no data has come yet so nothing to push.
Then, we are stuck forever as no further &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#push&lt;/code&gt; calls can occur because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_read&lt;/code&gt; will not be called anymore.&lt;/p&gt;

&lt;p&gt;Lucky for us, nothing prevents &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#push&lt;/code&gt;, or even &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#_read&lt;/code&gt; from being called from elsewhere in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; implementation.&lt;/p&gt;

&lt;p&gt;Let’s try again (and add a few temporary logs while we’re at it):&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// DO NOT USE IN PRODUCTION - SEE BELOW FOR DETAILS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Initializing pipeline&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Waiting for data, nothing to do for now...`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Data flowing: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; element(s) from each source to zip!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Chunk 1 received: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Waiting for data, calling with &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; element(s) from first upstream`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Chunk 2 received: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Waiting for data, calling with &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; element(s) from second upstream`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s re-run the test:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test
&lt;/span&gt;Initializing pipeline
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data, nothing to &lt;span class=&quot;k&quot;&gt;do for &lt;/span&gt;now...
Chunk 1 received: 1
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data, calling with 1 element&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; from first upstream
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data, nothing to &lt;span class=&quot;k&quot;&gt;do for &lt;/span&gt;now...
Chunk 2 received: Un
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data, calling with 1 element&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; from second upstream
Data flowing: 1 element&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; from each &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;to zip!
Chunk 1 received: 2
Chunk 2 received: Deux
Chunk 1 received: 3
Chunk 2 received: Trois
Data flowing: 2 element&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; from each &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;to zip!
Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;data, nothing to &lt;span class=&quot;k&quot;&gt;do for &lt;/span&gt;now...

Failures:
1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; zip operator &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; pairs chunks from upstream streams
  Message:
    Error: Timeout - Async &lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;did not &lt;span class=&quot;nb&quot;&gt;complete &lt;/span&gt;within 5000ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;by jasmine.DEFAULT_TIMEOUT_INTERVAL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hmm, the test still fails, but the implementation seems to behave correctly.
What actually happens is that our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZipReadable&lt;/code&gt; implementation never completes.
Looking again at the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_readable_push_chunk_encoding&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#push&lt;/code&gt;&lt;/a&gt; documentation,
we can see pushing that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; notifies downstream consumers that the stream is done emitting data.&lt;/p&gt;

&lt;p&gt;Now, when should we do that?
If we look at the Reactor diagram of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/zip.svg&quot; alt=&quot;`zip` diagram&quot; title=&quot;`zip` diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;… we can see that the completion should be sent when the last stream completes.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable&lt;/code&gt; streams notify consumers with the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_event_end&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end&lt;/code&gt; event&lt;/a&gt; when they are done.
Now that we have got everything figured out, let’s get rid of the logs and fix our implementation:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// DO NOT USE IN PRODUCTION - SEE BELOW FOR DETAILS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readyChunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readyChunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;we introduce a counter to keep track of upstream stream completion.&lt;/li&gt;
  &lt;li&gt;we observe each upstream stream completion and increment the counter when than occurs.&lt;/li&gt;
  &lt;li&gt;we notify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; stream completion when all upstream streams are done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s run the tests:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test

&lt;/span&gt;2 specs, 0 failures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yay, it passes 🥳&lt;/p&gt;

&lt;p&gt;However, the implementation could definitely be refactored as there is a lot of duplicated behaviors.
It could even be generalized to &lt;em&gt;n&lt;/em&gt; upstream sources (the corresponding test is very similar to the one with 2 sources)!&lt;/p&gt;

&lt;p&gt;And here we go:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// DO NOT USE IN PRODUCTION - SEE BELOW FOR DETAILS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ZipReadable&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Readable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;upstreams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (1)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;objectMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;streams&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upstreams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upstreams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (2)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// (3)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;curr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// (4)&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;previous&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]];&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previous&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;curr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;_startReading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;streams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endedUpstreamCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;streams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (5)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;streamChunks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;streamChunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitingForData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;streamChunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;we use now the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters&quot;&gt;“rest parameter” syntax&lt;/a&gt; to accept any number of streams.
We could arguably improve the signature further by having two mandatory streams and an optional rest ones for extra streams.&lt;/li&gt;
  &lt;li&gt;we just have to create an initial empty array of chunks for every stream&lt;/li&gt;
  &lt;li&gt;we compute the current length of each chunk array and use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax&quot;&gt;“spread syntax”&lt;/a&gt; to fit these lengths into separate arguments of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Math.min&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;finally, after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array#splice&lt;/code&gt; extract the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bound&lt;/code&gt; first parameter of each chunk array, these arrays are reduced into pairs and then published via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#push&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;the counter now need to reflect the dynamic number of upstream sources instead of the hardcoded 2 of the previous version&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Does the existing test still pass?&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;test

&lt;/span&gt;2 specs, 0 failures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Yes!&lt;/p&gt;

&lt;h2 id=&quot;one-more-thing&quot;&gt;One More Thing&lt;/h2&gt;

&lt;p&gt;There is one (albeit very important) aspect of streams I deliberately did not mention here: &lt;a href=&quot;https://nodejs.org/es/docs/guides/backpressuring-in-streams/&quot;&gt;backpressure&lt;/a&gt;.
Backpressure happens when downstream streams cannot keep up with upstream streams. Basically, the latter conveys data too fast for the first.&lt;/p&gt;

&lt;p&gt;The good news is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Readable#pipe&lt;/code&gt; handles backpressure “for free” (and I assume &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt; as well).&lt;/p&gt;

&lt;p&gt;That being said, do our custom implementations of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; &lt;a href=&quot;https://nodejs.org/en/docs/guides/backpressuring-in-streams/#rules-to-abide-by-when-implementing-custom-streams&quot;&gt;handle backpressure correctly&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Spoiler alert: I’m afraid not.&lt;/p&gt;

&lt;p&gt;However, there will be a dedicated blog post about this, with updates to the initial implementations 😉&lt;/p&gt;

&lt;h2 id=&quot;going-further&quot;&gt;Going further&lt;/h2&gt;

&lt;p&gt;If you notice improvements (other than backpressure-related ones), please send a &lt;a href=&quot;https://github.com/fbiville/fbiville.github.io&quot;&gt;Pull Request&lt;/a&gt; and/or reach out to me on &lt;a href=&quot;https://twitter.com/fbiville&quot;&gt;Twitter&lt;/a&gt;.
Here are a few references that helped me in my stream learning journey that are worth sharing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/stream.html&quot;&gt;https://nodejs.org/api/stream.html&lt;/a&gt;: the official documentation of Node.js streams, including implementation guides&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/nodejs/help/&quot;&gt;https://github.com/nodejs/help/&lt;/a&gt;: stuck with something? Open an issue in this repository and Node.js maintainers will help you!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/streams-api/&quot;&gt;https://www.w3.org/TR/streams-api/&lt;/a&gt; W3C/WhatWG stream spec (it slightly differs from Node.js stream API, but many concepts overlap)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://v8.dev/blog&quot;&gt;https://v8.dev/blog&lt;/a&gt;: not directly related to streams, but this blog authored by v8 maintainers is a goldmine of information w.r.t. how v8 works and new Javascript features&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Florent Biville</name></author><summary type="html">I joined the riff team at Pivotal a year and a half ago. I have been working for more than a year on riff invokers. This probably deserves a blog post on its own, but invokers, in short, have the responsibility of invoking user-defined functions and exposing a way to send inputs and receive outputs. The riff invocation protocol formally defines the scope of such invokers.</summary></entry><entry><title type="html">Hello Jekyll!</title><link href="https://fbiville.github.io/2019/05/19/Hello_Jekyll_.html" rel="alternate" type="text/html" title="Hello Jekyll!" /><published>2019-05-19T00:00:00+00:00</published><updated>2019-05-19T00:00:00+00:00</updated><id>https://fbiville.github.io/2019/05/19/Hello_Jekyll_</id><content type="html" xml:base="https://fbiville.github.io/2019/05/19/Hello_Jekyll_.html">&lt;p&gt;After a few issues with &lt;a href=&quot;https://github.com/HubPress/hubpress.io&quot;&gt;Hubpress.io&lt;/a&gt; (is it even maintained now?), I decided to migrate my blog again and move to Jekyll.&lt;/p&gt;

&lt;p&gt;The process was a mix of automatic (&lt;a href=&quot;https://pandoc.org/&quot;&gt;Pandoc&lt;/a&gt;), semi-manual (helped with some good old Bash commands) and purely manual transformations. I even fixed old quirks from the previous Dotclear-&amp;gt;Hubpress migration in the process.&lt;/p&gt;

&lt;p&gt;The theme is used is well… minimal but I do not really need a fancy blog. I got rid of the analytics. I also added a mystery page.&lt;/p&gt;

&lt;p&gt;Anyway, my blog is now live and usable again.&lt;/p&gt;

&lt;p&gt;Stay tuned for an announcement I have been wanting to make for a while!&lt;/p&gt;

&lt;p&gt;In the meantime, long live Jekyll!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/jekyll.png&quot; alt=&quot;Jekyll&quot; /&gt;&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">After a few issues with Hubpress.io (is it even maintained now?), I decided to migrate my blog again and move to Jekyll.</summary></entry><entry><title type="html">hack.commit.push</title><link href="https://fbiville.github.io/2019/05/19/hack.commit.push.html" rel="alternate" type="text/html" title="hack.commit.push" /><published>2019-05-19T00:00:00+00:00</published><updated>2019-05-19T00:00:00+00:00</updated><id>https://fbiville.github.io/2019/05/19/hack.commit.push</id><content type="html" xml:base="https://fbiville.github.io/2019/05/19/hack.commit.push.html">&lt;p&gt;&lt;a href=&quot;https://hack-commit-pu.sh&quot;&gt;hack.commit.push&lt;/a&gt; est un nouvel événement gratuit autour des projets libres / open-source qui débarque bientôt à Paris !&lt;/p&gt;

&lt;p&gt;Avant d’entrer dans les détails, je voulais revenir sur les motivations qui m’ont poussé à le co-créer.&lt;/p&gt;

&lt;h1 id=&quot;tldr&quot;&gt;&lt;abbr title=&quot;Too Long; Didn't Read&quot;&gt;TL;DR&lt;/abbr&gt;&lt;/h1&gt;

&lt;p&gt;Pas envie de tout lire ?
Vous pouvez aller &lt;a href=&quot;#save-the-date&quot;&gt;droit à l’essentiel&lt;/a&gt; avec les infos à retenir.&lt;/p&gt;

&lt;h1 id=&quot;la-source--hackergarten-paris&quot;&gt;La source : Hackergarten Paris&lt;/h1&gt;

&lt;p&gt;Le meetup &lt;a href=&quot;https://www.meetup.com/Paris-Hackergarten/&quot;&gt;Hackergarten Paris&lt;/a&gt; réunit contributeur·trice·s de projets libres/open-source et personnes désireuses de s’y mettre sans nécessairement savoir par où commencer.&lt;/p&gt;

&lt;p&gt;Comme expliqué dans &lt;a href=&quot;https://fbiville.github.io/2016/09/20/Pourquoi-venir-au-Hackergarten.html&quot;&gt;une publication précédente&lt;/a&gt;, l’avantage est multiple.&lt;/p&gt;

&lt;p&gt;Les nouveaux·elles venu·e·s sont accompagné·e·s en direct par une personne familière avec le code à changer. Elles peuvent donc contribuer efficacement, prendre confiance et aussi démystifier le travail accompli : &lt;strong&gt;vous aussi&lt;/strong&gt; êtes capable de contribuer !&lt;/p&gt;

&lt;p&gt;Côté project leads, une récente enquête (en anglais) de l’excellente initiative &lt;a href=&quot;https://opencollective.com/&quot;&gt;Open Collective&lt;/a&gt; résume bien mieux que moi l’un des besoins que les meetups Hackergarten ont pour ambition de satisfaire.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-partner=&quot;tweetdeck&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;One of the core reasons why the &lt;a href=&quot;https://twitter.com/hackcommitpush?ref_src=twsrc%5Etfw&quot;&gt;@hackcommitpush&lt;/a&gt; conference and the &lt;a href=&quot;https://twitter.com/Hackergarten?ref_src=twsrc%5Etfw&quot;&gt;@hackergarten&lt;/a&gt; meetups exist is perfectly summed up in this &lt;a href=&quot;https://twitter.com/opencollect?ref_src=twsrc%5Etfw&quot;&gt;@opencollect&lt;/a&gt; survey: &lt;a href=&quot;https://t.co/qmOEIkKAdr&quot;&gt;https://t.co/qmOEIkKAdr&lt;/a&gt;. Worth reading and sharing!&lt;br /&gt;Looking forward to welcoming contributors on June 15: &lt;a href=&quot;https://t.co/skQvuterrd&quot;&gt;https://t.co/skQvuterrd&lt;/a&gt;! &lt;a href=&quot;https://t.co/yYDiHtRHBO&quot;&gt;pic.twitter.com/yYDiHtRHBO&lt;/a&gt;&lt;/p&gt;&amp;mdash; hack.commit.push (@hackcommitpush) &lt;a href=&quot;https://twitter.com/hackcommitpush/status/1129324028735438848?ref_src=twsrc%5Etfw&quot;&gt;May 17, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;En effet, la plupart des projets libres/open-source sont maintenus par des personnes distribuées sur toute la planète et la communication s’effectue habituellement par écrans interposés.&lt;/p&gt;

&lt;p&gt;Le meetup Hackergarten Paris (&lt;a href=&quot;http://hackergarten.net/&quot;&gt;comme ceux d’autres villes&lt;/a&gt;) permet donc de co-localiser les personnes motivées par un sujet commun et de les faire avancer dans un cadre détendu et bienveillant. En bref, retisser un lien social qui se perd entre contributeur·rice·s.&lt;/p&gt;

&lt;h1 id=&quot;hackcommitpush-dans-tout-ça-&quot;&gt;hack.commit.push dans tout ça ?&lt;/h1&gt;

&lt;p&gt;J’ai d’excellents souvenirs de mes premières participations au Hackergarten autour de 2011-2012. Il avait lieu régulièrement à &lt;a href=&quot;https://xebia.com/&quot;&gt;Xebia&lt;/a&gt; et était organisé par &lt;a href=&quot;https://twitter.com/mathildelemee&quot;&gt;Mathilde&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/BriceDutheil&quot;&gt;Brice&lt;/a&gt; et &lt;a href=&quot;https://twitter.com/elefevre&quot;&gt;Éric&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Néanmoins, faute de temps, le meetup ne fut plus organisé que pendant les grandes conférences (Devoxx etc).
Avec la permission des trois organisateurs cités ci-dessus, j’ai alors repris le meetup (fin 2015, de mémoire) et relancé sa version mensuelle (qui continue aujourd’hui : tous les derniers mardis du mois à &lt;a href=&quot;https://www.meetup.com/Paris-Hackergarten/&quot;&gt;Paris&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;J’ai même essayé deux ou trois fois de tenir le Hackergarten pendant &lt;a href=&quot;https://devoxx.fr&quot;&gt;Devoxx France&lt;/a&gt;, après sa migration au Palais des Congrès. Pour des raisons diverses, cela n’a simplement pas fonctionné : quasiment personne n’a rejoint la session.&lt;/p&gt;

&lt;p&gt;Au delà des améliorations d’organisation potentielles de Devoxx pour le Hackergarten (les orgas abattent déjà un travail considérable), j’ai fini par me demander s’il était vraiment pertinent de proposer un Hackergarten à des personnes venues avant tout pour assister à des conférences et pour réseauter.&lt;/p&gt;

&lt;p&gt;C’est de ce constat qu’est né l’idée du &lt;a href=&quot;https://hack-commit-pu.sh&quot;&gt;hack.commit.push&lt;/a&gt; est né : un événement 100% dédié aux contributions de projets libres / open-source, à la manière des Hackergartens existants !&lt;/p&gt;

&lt;h1 id=&quot;save-the-date&quot;&gt;Save the date&lt;/h1&gt;

&lt;p&gt;Organisée par &lt;a href=&quot;https://twitter.com/aalmiray&quot;&gt;Andres&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/hboutemy&quot;&gt;Hervé&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/mesirii&quot;&gt;Michael&lt;/a&gt; et votre serviteur, soutenue par des contributeur·trice·s tel·le·s que &lt;a href=&quot;https://twitter.com/JessicaGantier&quot;&gt;Jessica&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/dyild&quot;&gt;Dilek&lt;/a&gt; et &lt;a href=&quot;https://twitter.com/kehrlann&quot;&gt;Daniel&lt;/a&gt;, la première édition est &lt;strong&gt;GRATUITE&lt;/strong&gt;, aura lieu le &lt;strong&gt;15 Juin&lt;/strong&gt; à &lt;strong&gt;Paris&lt;/strong&gt; dans les très beaux locaux de &lt;a href=&quot;http://www.techandcodefactory.fr/&quot;&gt;Tech &amp;amp; Code Factory&lt;/a&gt; et s’inscrit dans la droite lignée des Hackergartens :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;tou·te·s les participant·e·s sont bienvenu·e·s, quel que soit leur niveau en développement logiciel et leur expérience avec des projets libres / open-source&lt;/li&gt;
  &lt;li&gt;que ce soit de l’amélioration de documentation, de design, de correction de bugs ou de l’ajout de fonctionnalité, chaque contribution compte !&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour les débutant·e·s, nous avons pour volonté d’organiser des ateliers d’introduction la matinée (par exemple : introduction à Git / Github) afin de les aider à contribuer pendant l’après-midi.&lt;/p&gt;

&lt;p&gt;Nous avons d’ores et déjà de beaux projets à vous proposer :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://maven.apache.org/&quot;&gt;Apache Maven&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://neo4j.com/&quot;&gt;Neo4j&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://projectriff.io/&quot;&gt;riff&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/home/&quot;&gt;Kubernetes FR docs&lt;/a&gt; &amp;lt;- un grand merci à &lt;a href=&quot;https://twitter.com/remyleone&quot;&gt;Rémy Leone&lt;/a&gt; de &lt;a href=&quot;https://www.scaleway.com/en/betas/&quot;&gt;Scaleway&lt;/a&gt; au passage&lt;/li&gt;
  &lt;li&gt;et bien d’autres !&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;N’hésitez plus, &lt;a href=&quot;https://hack-commit-pu.sh/&quot;&gt;inscrivez-vous&lt;/a&gt; et faites passer le mot !&lt;/p&gt;

&lt;p&gt;Vous souhaitez vous impliquer davantage ? Lisez ce qui suit ↓&lt;/p&gt;

&lt;h1 id=&quot;je-veux-mimpliquer-&quot;&gt;Je veux m’impliquer !&lt;/h1&gt;

&lt;h2 id=&quot;je-veux-proposer-un-projet&quot;&gt;Je veux proposer un projet&lt;/h2&gt;

&lt;p&gt;Votre mission, si vous l’acceptez, est d’accompagner de façon bienveillante des personnes au niveau varié sur leurs premières contributions à votre projet libre/open-source.&lt;/p&gt;

&lt;p&gt;Votre challenge sera d’équilibrer le temps d’explication nécessaire pour commencer à contribuer (vous voulez maximiser la participation des contributeur·trice·s) et le temps effectif de contribution (vous pouvez définir des pré-requis pour être plus efficace, mais c’est au risque d’exclure d’emblée trop de participant·e·s).&lt;/p&gt;

&lt;p&gt;Toujours tenté·e·s ? Alors, n’hésitez pas à nous envoyer, de préférence en anglais, une description de votre projet et les contributions possibles en une journée (avec d’éventuels pré-requis pour les participant·e·s) : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization AT hack-commit-pu.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ma-société-veut-sponsoriser&quot;&gt;Ma société veut sponsoriser&lt;/h2&gt;

&lt;p&gt;Nous avons en effet divers frais à couvrir, tels que le buffet de la journée, le cocktail de clôture et pourquoi pas encore d’autres services si le budget le permet.&lt;/p&gt;

&lt;p&gt;Pour information, nous sommes structurés en &lt;a href=&quot;https://paris-springers.github.io/&quot;&gt;association&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;N’hésitez pas à nous contacter, de préférence en anglais, à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization AT hack-commit-pu.sh&lt;/code&gt; pour que nous vous envoyions notre prospectus.&lt;/p&gt;

&lt;h2 id=&quot;je-veux-animer-un-atelier-dintroduction&quot;&gt;Je veux animer un atelier d’introduction&lt;/h2&gt;

&lt;p&gt;Nous avons à coeur que les profils moins expérimentés puissent également participer. Le but des ateliers d’introduction est d’adresser, en deux heures, les fondamenteux de technologies utiles aux différents projets représentés pendant l’événement.&lt;/p&gt;

&lt;p&gt;Le candidat le plus évident est Git / Github.&lt;/p&gt;

&lt;p&gt;N’hésitez pas à nous contacter, de préférence en anglais, à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization AT hack-commit-pu.sh&lt;/code&gt; si cette opportunité vous intéresse.&lt;/p&gt;

&lt;h2 id=&quot;je-veux-être-bénévole&quot;&gt;Je veux être bénévole&lt;/h2&gt;

&lt;p&gt;Si vous voulez rejoindre l’aventure, n’hésitez pas à nous contacter, de préférence en anglais, à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization AT hack-commit-pu.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Si vous ne voulez aider “que” pendant le jour J, voici un aperçu de ce qu’il est possible de faire :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;accueil des sponsors / project leads&lt;/li&gt;
  &lt;li&gt;inscription des participant·e·s&lt;/li&gt;
  &lt;li&gt;annonce des pauses&lt;/li&gt;
  &lt;li&gt;aide au ménage en fin de journée&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ce qui n’est pas incompatible avec une participation à l’événement en lui-même (vous aurez juste un temps de participation un peu plus réduit) !&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">hack.commit.push est un nouvel événement gratuit autour des projets libres / open-source qui débarque bientôt à Paris !</summary></entry><entry><title type="html">Pourquoi Venir Au Hackergarten</title><link href="https://fbiville.github.io/2016/09/20/Pourquoi-venir-au-Hackergarten.html" rel="alternate" type="text/html" title="Pourquoi Venir Au Hackergarten" /><published>2016-09-20T00:00:00+00:00</published><updated>2016-09-20T00:00:00+00:00</updated><id>https://fbiville.github.io/2016/09/20/Pourquoi-venir-au-Hackergarten</id><content type="html" xml:base="https://fbiville.github.io/2016/09/20/Pourquoi-venir-au-Hackergarten.html">&lt;p&gt;Qu’on se le dise, les logiciels Open Source sont partout. Il y a fort à
parier que vous les utilisiez directement voire en développiez dans
votre activité professionnelle. Il demeure indéniable que vous en
bénéficiez dans votre vie quotidienne, même indirectement.&lt;/p&gt;

&lt;h1 id=&quot;hackers-we-need-you&quot;&gt;Hackers: we need you!&lt;/h1&gt;

&lt;p&gt;Il vous est peut-être même arrivé de renseigner un bug, voire de
soumettre un correctif à un logiciel open-source que vous utilisez dans
le cadre professionnel. Mais en dehors de ces rares occasions, vous
n’avez jamais trouvé le temps de contribuer de façon plus pérenne.&lt;/p&gt;

&lt;p&gt;Pourtant, en voilà un objectif qui peut rendre fier|fière ! Devenir
l’un des committers principaux d’un projet visible (ou en passe de le
devenir) peut faire une belle différence sur le CV et dans votre
carrière.&lt;/p&gt;

&lt;p&gt;Cela ne se fait évidemment pas en un jour, mais chaque première
contribution est importante. Il peut être assez difficile de se plonger
dans une base de code inconnue sans aide extérieure, ni objectif précis.&lt;/p&gt;

&lt;p&gt;Paris Hackergarten est là pour vous !&lt;/p&gt;

&lt;p&gt;Il vise à regrouper, dans une même pièce, le temps d’une soirée (1 fois
par mois), committers confirmés (a.k.a. mentors) et contributeurs
motivés (a.k.a. hackers) !&lt;/p&gt;

&lt;p&gt;Chacun y retrouve son compte :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;le mentor voit son projet avancer grâce aux contributions&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;le hacker se familiarise avec la base de code, avec l’aide du mentor
et envoie ses premières contributions en quelques heures, et non pas
en quelques jours&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lors de la dernière soirée, un binôme a réussi à soumettre une &lt;a href=&quot;https://github.com/apache/maven-shared/pull/13&quot;&gt;pull
request&lt;/a&gt; au projet
Apache Maven ! Ils ont pourtant commencé la soirée sans connaissances
préalables de la base de code. Merci à Hervé pour le mentoring au
passage !&lt;/p&gt;

&lt;p&gt;Tous les hackers sont bienvenus ! Ne vous auto-censurez pas en pensant
que vous n’avez pas le niveau, ça n’est pas vrai ! ;-)&lt;/p&gt;

&lt;h1 id=&quot;appel-aux-mentors&quot;&gt;Appel aux mentors&lt;/h1&gt;

&lt;p&gt;Vous souhaitez présenter votre projet et attirer de nouvelles
contributions ?&lt;/p&gt;

&lt;p&gt;Pour se faire, deux règles sont en vigueur :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;préparer une présentation deux minutes afin de familiariser et
&quot;vendre&quot; votre projet aux participants&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;avoir un ensemble de tâches bien définies, idéalement réalisables en
une soirée&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Concernant la technologie employée : aucune contrainte !&lt;/p&gt;

&lt;p&gt;Je tiens à insister sur ce point. On pourrait croire actuellement que le
meetup est réservé aux développeurs Java, ça n’est pas le cas !&lt;/p&gt;

&lt;p&gt;Il se peut même qu’une session du Paris Hackergarten soit prochainement
dédiée au développement iOS, stay tuned! ;-)&lt;/p&gt;

&lt;h1 id=&quot;à-vos-calendriers-&quot;&gt;À vos calendriers !&lt;/h1&gt;

&lt;p&gt;Nous nous efforçons d’organiser le &lt;a href=&quot;http://www.meetup.com/Paris-Hackergarten/&quot;&gt;Paris
Hackergarten&lt;/a&gt; tous les
derniers mardis du mois, dans les locaux de Xebia.&lt;/p&gt;

&lt;p&gt;Le
&lt;a href=&quot;http://www.meetup.com/Paris-Hackergarten/events/231855753/&quot;&gt;prochain&lt;/a&gt;
aura donc lieu le 27 Septembre, j’espère donc vous y voir !&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">Qu’on se le dise, les logiciels Open Source sont partout. Il y a fort à parier que vous les utilisiez directement voire en développiez dans votre activité professionnelle. Il demeure indéniable que vous en bénéficiez dans votre vie quotidienne, même indirectement.</summary></entry><entry><title type="html">Rant: The Teletubbies “Documentation” Pitfall</title><link href="https://fbiville.github.io/2016/09/19/Rant-The-Teletubbies-Documentation-Pitfall.html" rel="alternate" type="text/html" title="Rant: The Teletubbies “Documentation” Pitfall" /><published>2016-09-19T00:00:00+00:00</published><updated>2016-09-19T00:00:00+00:00</updated><id>https://fbiville.github.io/2016/09/19/Rant-The-Teletubbies-Documentation-Pitfall</id><content type="html" xml:base="https://fbiville.github.io/2016/09/19/Rant-The-Teletubbies-Documentation-Pitfall.html">&lt;h1 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;/h1&gt;

&lt;p&gt;I am not Uncle Bob’s nephew, but if you already have read Clean Code,
chances are you will not learn much from this post.&lt;/p&gt;

&lt;h1 id=&quot;typical-example&quot;&gt;Typical example&lt;/h1&gt;

&lt;p&gt;Let me talk about a coding practice that I find profoundly disturbing.
Get this code for instance:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;public SomeResult computeResult(SomeParameter parameter) {
    // call nice service to fetch foo
    Foo foo = niceService.fetchFoo(parameter);
    return new SomeResult(foo);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Basically, we have got some trivial calls to a service and use it for
instanciating the result we are interested in.&lt;/p&gt;

&lt;p&gt;Do we need the comment, though? Obviously, we don’t!&lt;/p&gt;

&lt;p&gt;We are just adding noise!&lt;/p&gt;

&lt;p&gt;That’s why I call it a Teletubbies documentation.&lt;/p&gt;

&lt;h1 id=&quot;teletu-what&quot;&gt;Teletu-what?&lt;/h1&gt;

&lt;p&gt;Teletubbies, as you probably already know, is a TV show for very young
children, created by the BBC.&lt;/p&gt;

&lt;p&gt;If you know the show, you know also that whenever a Teletubbies
character does something, the following happens:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;the character announces what it intends to do&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the voice-over paraphrases what the character just said&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the character does it&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;optionally back to step 1&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes sense for very young children, part of education is based on
repetition.&lt;/p&gt;

&lt;h1 id=&quot;back-to-our-example&quot;&gt;Back to our example&lt;/h1&gt;

&lt;p&gt;So whenever I encounter a snippet of code like above, I immediately hear
this annoying voice-over that just repeats something we already know.&lt;/p&gt;

&lt;p&gt;It is annoying because, well, we are not very young children.&lt;/p&gt;

&lt;p&gt;What’s the big deal, you might object?&lt;/p&gt;

&lt;p&gt;Well, comments like these can &lt;strong&gt;easily&lt;/strong&gt; get out of sync. In the
worst-case scenario, they become misleading.&lt;/p&gt;

&lt;p&gt;It leads to situations where you have to confront the current code and
the outdated comment and you cannot really be sure which one describes
what the behavior &lt;strong&gt;should&lt;/strong&gt; be.&lt;/p&gt;

&lt;p&gt;Comments don’t run, they are just an informal bunch of text and cannot
be changed automatically (at least, not in a 100% reliable way). Their
risk of becoming obsolete is therefore higher.&lt;/p&gt;

&lt;p&gt;To rephrase it, comments like this are part of the problem, not the
solution.&lt;/p&gt;

&lt;p&gt;Inline comments are just a liability.&lt;/p&gt;

&lt;p&gt;The worst part is that they often appear as a whole bunch:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;public SomeResult computeResult(SomeParameter parameter) {
    // call nice service to fetch foo
    Foo foo = niceService.fetchFoo(parameter);

    // [...] 200 lines with comments+code like that
    // hilarity ensues... not
    return new SomeResult(foo, ...);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Indeed, the bad side effect of this kind of brain-dead comments is that
it &lt;strong&gt;prevents&lt;/strong&gt; the original authors to ask themselves: is the code
readable enough this way? Am I thinking this through? How can I make the
code more self-explanatory?&lt;/p&gt;

&lt;p&gt;If you get used to this kind of comments, you will most likely focus
your reading on them and live in the illusion that the method is
readable and well-documented.&lt;/p&gt;

&lt;p&gt;I have got some bad news for you: 200 lines of code for a method are NOT
readable at all, no matter how much obsolete poetry you stick in there.&lt;/p&gt;

&lt;p&gt;As a general rule of thumb, is it worth writing something down if that
only took you 10 seconds to come up with?&lt;/p&gt;

&lt;h1 id=&quot;a-not-so-noisy-example&quot;&gt;A not-so-noisy example&lt;/h1&gt;

&lt;p&gt;Let’s move on to a more interesting example.&lt;/p&gt;

&lt;p&gt;It’s not that the first example does not happen frequently, but there
are some situations like the following that involves a bit more than
pure noise.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;public SomeResult computeResult(SomeParameter parameter) {
    /*
     * call nice service to fetch foo because
     * some contextual reasons
     *
     * fetchFoo may throw in theory but will not
     * because the parameter is always valid in
     * this particular usecase [...], so no try-catch,
     * YOLO
     */
    Foo foo = niceService.fetchFoo(parameter);
    return new SomeResult(foo);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&quot;Ah! This comment is useful! It explains the implementation
rationale!”, you may say.&lt;/p&gt;

&lt;p&gt;While there is some value in these pieces of information, they just do
not belong there.&lt;/p&gt;

&lt;p&gt;Let me elaborate.&lt;/p&gt;

&lt;h1 id=&quot;small-detour-back-to-basics&quot;&gt;Small detour: back to basics&lt;/h1&gt;

&lt;p&gt;As you already know, in many programming languages, method signatures
look like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;public SomeResult computeResult(SomeParameter parameter)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ideally, the signature should be explicit enough (especially with
well-defined types, parametricity FTW) to know what the method does. How
the method does it should be relevant only if you have to change
something there.&lt;/p&gt;

&lt;p&gt;Everything that follows between curly braces is about &lt;strong&gt;implementation&lt;/strong&gt;
details.&lt;/p&gt;

&lt;h1 id=&quot;back-to-the-example-again&quot;&gt;Back to the example again&lt;/h1&gt;

&lt;p&gt;However, I would argue that the two information encoded as a inline
comment above are NOT implementation details, yet they live in the
implementation section.&lt;/p&gt;

&lt;p&gt;What are these comment sections about?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;the first part describe the intent behind the implementation (or at
least part of it)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the second and last part describe (part of) the observable behavior
of the method&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;intent-documentation&quot;&gt;Intent documentation&lt;/h1&gt;

&lt;p&gt;Intents are very contextual and temporal.&lt;/p&gt;

&lt;p&gt;Decisions, no matter how small, are taken every day and guide the way we
implement things.&lt;/p&gt;

&lt;p&gt;These decisions are influenced by temporal factors mostly: the
assumptions made at the time may not hold at all anymore in 6 months, 1
year…​&lt;/p&gt;

&lt;p&gt;Temporal documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TEMPORAL&lt;/strong&gt; documentation.&lt;/p&gt;

&lt;p&gt;It rings a bell, somehow.&lt;/p&gt;

&lt;p&gt;S-C-M! Source Control Management tools like Git, Mercurial and friends.&lt;/p&gt;

&lt;p&gt;They play an important part in documentation. Not only do they
intrinsically describe what has changed and when, they should describe
&lt;strong&gt;why&lt;/strong&gt; the changes were made.&lt;/p&gt;

&lt;p&gt;That’s what &lt;strong&gt;commit messages&lt;/strong&gt; are for!&lt;/p&gt;

&lt;p&gt;And if you start thinking this way, there will be an additional benefit:
you will keep your commits as small and focused as possible. If the
commit is too big, there is no way you can explain all the important
changes you made ;-)&lt;/p&gt;

&lt;p&gt;And if you start to care enough about your changelog, you will get nice
readable releases notes for free!&lt;/p&gt;

&lt;h1 id=&quot;observable-behavior-documentation&quot;&gt;Observable behavior documentation&lt;/h1&gt;

&lt;p&gt;If what you describe is part of the observable behavior of the scope you
are modifying, then it is clearly about the contract you implicitly sign
between the code you are implementing and its callers.&lt;/p&gt;

&lt;p&gt;The documentation is about the API. API is just a clever name for a set
of accessible signatures. It is not an implementation detail at all, it
should be near the method signature itself:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;/**
 * *describes the nominal observable behaviour here [...]*
 *
 * fetchFoo may throw in theory but will not
 * because the parameter is always valid in this
 * particular usecase [...], so no try-catch, YOLO
 */
public SomeResult computeResult(SomeParameter parameter) {
    Foo foo = niceService.fetchFoo(parameter);
    return new SomeResult(foo);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;going-further&quot;&gt;Going further&lt;/h1&gt;

&lt;p&gt;You could even rewrite the method like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;/**
 * *describes the nominal observable behaviour here [...]*
 */
public SomeResult computeResult(SomeParameter parameter) {
    try {
        Foo foo = niceService.fetchFoo(parameter);
        return new SomeResult(foo);
    }
    catch (MyNiceServiceException e) {
        throw new AssertionError(&quot;Should not happen&quot;, e);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now the assumptions are even more explicit. That opens even an
interesting discussion about the virtues of &lt;a href=&quot;https://www.youtube.com/watch?v=57P86oZXjXs&quot;&gt;failing
fast&lt;/a&gt; :-)&lt;/p&gt;

&lt;p&gt;One could argue we could do even better. Ideally, method signatures
should be sufficient to tell what the method is doing:
&lt;a href=&quot;http://data.tmorris.net/talks/yow-west-2016/1d388b6263e7cbeedfbea224997648daa1d7862d/parametricity.pdf&quot;&gt;parametricity&lt;/a&gt;
FTW! Hoogle.com is probably one of the best illustrations for this.&lt;/p&gt;

&lt;p&gt;That requires discipline (especially with languages such as Java, C# et
al), but is not impossible to achieve: try to minimize and contain side
effects, forego nulls…​ and then types could convery a lot more useful
information!&lt;/p&gt;

&lt;p&gt;Yet another interesting discussion!&lt;/p&gt;

&lt;h1 id=&quot;the-end&quot;&gt;The end&lt;/h1&gt;

&lt;p&gt;As you can see, caring about documentation is a gateway drug to better
software, clearer releases and happier collaborators.&lt;/p&gt;

&lt;p&gt;I personally write comments less than 1% of the time I write code. This
happens where there is a tiny local expression that may seem obscure and
there is not simple way around it.&lt;/p&gt;

&lt;p&gt;For the 99+%, there are almost always better places to write the
information you want to convey:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;the code itself, it should answer &lt;strong&gt;WHAT&lt;/strong&gt; it does, without
ambiguity, else just refactor it (extract meaningful methods,
rename, split expressions…​ the IDE is your friend). This is the
material that decays the least, rely on this as much as you can!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the *-doc (e.g. Javadoc, Csharpdoc): the information is about the
observable behavior of the section you are altering&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the intent: that should justify the commit you are about to push&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inline comments are (99+%) dead! Long live inline comments!&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">Disclaimer</summary></entry><entry><title type="html">Compilers Hate Him! Discover This One Weird Trick with Neo4j Stored Procedures</title><link href="https://fbiville.github.io/2016/07/12/Compilers-hate-him-Discover-this-one-weird-trick-with-Neo4j-stored-procedures.html" rel="alternate" type="text/html" title="Compilers Hate Him! Discover This One Weird Trick with Neo4j Stored Procedures" /><published>2016-07-12T00:00:00+00:00</published><updated>2016-07-12T00:00:00+00:00</updated><id>https://fbiville.github.io/2016/07/12/Compilers-hate-him-Discover-this-one-weird-trick-with-Neo4j-stored-procedures</id><content type="html" xml:base="https://fbiville.github.io/2016/07/12/Compilers-hate-him-Discover-this-one-weird-trick-with-Neo4j-stored-procedures.html">&lt;p&gt;As you probably already know, Neo4j 3.0 finally comes with &lt;a href=&quot;https://neo4j.com/docs/java-reference/current/#_calling_procedure&quot;&gt;stored
procedures&lt;/a&gt;
(let’s call them sprocs from now on).&lt;/p&gt;

&lt;p&gt;The cool thing about this is you can directly interact with sprocs in
Cypher, as &lt;a href=&quot;https://twitter.com/mesirii&quot;&gt;Michael Hunger&lt;/a&gt; explains in
this &lt;a href=&quot;https://neo4j.com/blog/intro-user-defined-procedures-apoc/&quot;&gt;blog
post&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;writing-stored-procedures&quot;&gt;Writing stored procedures&lt;/h1&gt;

&lt;p&gt;During the preparation of my Neo4j introduction talk in the latest
&lt;a href=&quot;https://www.facebook.com/GoCriteo/photos/pcb.1045385882181102/1045385698847787/?type=3&quot;&gt;Criteo
summit&lt;/a&gt;
(we’re &lt;a href=&quot;http://www.criteo.com/careers/#careers-browser&quot;&gt;hiring&lt;/a&gt;!), I
started playing around with sprocs.&lt;/p&gt;

&lt;p&gt;The process is quite simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;You write some code, annotate it&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;test it with the test harness&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;package the JAR and deploy it to your Neo4j instance (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins/&lt;/code&gt;)!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Actually, step 3 may repeat itself quite a few times, Neo4j sprocs must
comply to a few rules before your Neo4j server accepts to deploy it.&lt;/p&gt;

&lt;h1 id=&quot;sproc-rules&quot;&gt;Sproc rules&lt;/h1&gt;

&lt;p&gt;The rules are detailed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@org.neo4j.procedure.Procedure&lt;/code&gt;
&lt;a href=&quot;https://github.com/neo4j/neo4j/blob/3.0/community/kernel/src/main/java/org/neo4j/procedure/Procedure.java#L31&quot;&gt;javadoc&lt;/a&gt;,
but we can summarize them as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;a sproc is a method annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@org.neo4j.procedure.Procedure&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;it must return a
&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java.util.stream.Stream&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt;
where T is a user-defined record type&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the record type must define public fields&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;these can only be of restricted types&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;if the sproc accepts parameters, they all must be annotated with
&lt;a href=&quot;https://github.com/neo4j/neo4j/blob/3.0/community/kernel/src/main/java/org/neo4j/procedure/Name.java&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@org.neo4j.procedure.Name&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;parameters can only be of specific types&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;the procedure name must be unique (name = package name+method name)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;injectable types (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphDatabaseService&lt;/code&gt; et al) must target public
non-static, non-final,
&lt;a href=&quot;https://github.com/neo4j/neo4j/blob/3.0/community/kernel/src/main/java/org/neo4j/procedure/Context.java&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Context&lt;/code&gt;-annotated&lt;/a&gt;
fields&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, folks at &lt;a href=&quot;https://neo4j.com/company/&quot;&gt;Neo Technology&lt;/a&gt; have
done a wonderful job at error reporting. Neo4j fails fast if any of the
rules is violated and gives a detailed error message.&lt;/p&gt;

&lt;p&gt;Here is an example with Neo4j 3.0.3 and the following &lt;strong&gt;failing&lt;/strong&gt;
attempt to deploy the following sproc:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;@Procedure
public Stream&amp;lt;MyRecord&amp;gt; doSomething(Map&amp;lt;String, Integer&amp;gt; value) {
    // [...]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following error will be prompted (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logs/neo4j.log&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Caused by: org.neo4j.kernel.api.exceptions.ProcedureException: Argument at position 0 in method `doSomething` is missing an `@Name` annotation.
Please add the annotation, recompile the class and try again.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nice error message! Just add the missing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Name&lt;/code&gt; on the only parameter,
re-compile, package and deploy the JAR again, restart Neo4j and you’re
done!&lt;/p&gt;

&lt;h1 id=&quot;can-we-do-better&quot;&gt;Can we do better?&lt;/h1&gt;

&lt;p&gt;The previous example is quite trivial, but this back-and-forth could be
potentially repeated many times, especially when one is not much
familiar with sprocs.&lt;/p&gt;

&lt;p&gt;Fortunately for us, most of the errors can be caught at compile time.&lt;/p&gt;

&lt;h1 id=&quot;eurekaannotation-processing-ftw&quot;&gt;@Eureka(&quot;annotation processing FTW!”)&lt;/h1&gt;

&lt;p&gt;Annotations have been around in Java since end of 2004 (v1.5) and have
come together with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt; (now built in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;javac&lt;/code&gt;), the annotation
processing tool.&lt;/p&gt;

&lt;p&gt;What the latter does in brief (in long, read the
&lt;a href=&quot;https://www.jcp.org/en/jsr/detail?id=269&quot;&gt;spec&lt;/a&gt;) is to allow
user-defined code to introspect a Java program at compile-time (original
paper &lt;a href=&quot;http://www.bracha.org/mirrors.pdf&quot;&gt;here&lt;/a&gt;) and possibly:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;issue compilation notices/warnings/errors&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;generate static, source and/or bytecode files&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(By the way, this means exceptions can be raised at compile-time too!)&lt;/p&gt;

&lt;p&gt;Based on this, I decided to write a little annotation processor on my
way back from Criteo summit (did I mention we are
&lt;a href=&quot;http://www.criteo.com/careers/#careers-browser&quot;&gt;hiring&lt;/a&gt;?).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/fbiville/neo4j-sproc-compiler&quot;&gt;neo4j-sproc-compiler&lt;/a&gt;
is born. And it’s
&lt;a href=&quot;https://github.com/neo4j-contrib/neo4j-apoc-procedures/blob/18fe85a3712aa84696cc4dedaf0db659a63e3e7b/pom.xml#L72&quot;&gt;used&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;If Michael is happy, I am happy:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/fbiville/fbiville.github.io/master/images/michael-sproc-compiler-feedback.png&quot; alt=&quot;michael sproc compiler
feedback&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(I swear it’s not photoshopped, see #apoc channel, 1st of July 2016 in
Neo4j-Users &lt;a href=&quot;https://neo4j-users.slack.com&quot;&gt;Slack&lt;/a&gt;).&lt;/p&gt;

&lt;h1 id=&quot;neo4j-sproc-compiler-in-action&quot;&gt;neo4j-sproc-compiler in action&lt;/h1&gt;

&lt;p&gt;While the following screencast features Maven, the annotation processor
is actually agnostic of any build tool. You can use any build tool you
want or directly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;javac&lt;/code&gt; if that floats your boat!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://asciinema.org/a/79379&quot;&gt;&lt;img src=&quot;https://asciinema.org/a/79379.svg&quot; alt=&quot;asciicast&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Be cautious, most but &lt;strong&gt;not&lt;/strong&gt; all checks can be performed at compile
time. You’ll still need to write some tests and monitor your deploys!&lt;/p&gt;

&lt;p&gt;Hopefully, this little utility that I wrote will shorten your
development feedback loop and get your stored procedures harder, better,
stronger and faster.&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">As you probably already know, Neo4j 3.0 finally comes with stored procedures (let’s call them sprocs from now on).</summary></entry><entry><title type="html">New Blog!</title><link href="https://fbiville.github.io/2015/05/03/New-blog.html" rel="alternate" type="text/html" title="New Blog!" /><published>2015-05-03T00:00:00+00:00</published><updated>2015-05-03T00:00:00+00:00</updated><id>https://fbiville.github.io/2015/05/03/New-blog</id><content type="html" xml:base="https://fbiville.github.io/2015/05/03/New-blog.html">&lt;p&gt;Getting rid of Dotclear was long overdue. Impractical at best, I wasted
way too much time polishing the contents so that it would not render too
bad.&lt;/p&gt;

&lt;h1 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h1&gt;

&lt;p&gt;I need to automate the migration to HubPress, so it will take some more
time before all my blog posts show up here. For now,
&lt;a href=&quot;http://florent.biville.net&quot;&gt;http://florent.biville.net&lt;/a&gt; is still serving my old blog.&lt;/p&gt;

&lt;p&gt;It’s just a matter of time before everything is fully set up ;)&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">Getting rid of Dotclear was long overdue. Impractical at best, I wasted way too much time polishing the contents so that it would not render too bad.</summary></entry><entry><title type="html">Transfert Estival</title><link href="https://fbiville.github.io/2014/10/05/Transfert-estival.html" rel="alternate" type="text/html" title="Transfert Estival" /><published>2014-10-05T00:00:00+00:00</published><updated>2014-10-05T00:00:00+00:00</updated><id>https://fbiville.github.io/2014/10/05/Transfert-estival</id><content type="html" xml:base="https://fbiville.github.io/2014/10/05/Transfert-estival.html">&lt;h1 id=&quot;mais-pourquoi-&quot;&gt;Mais pourquoi ?!&lt;/h1&gt;

&lt;p&gt;Pour avoir un dictionnaire chaque année, bien sûr ! (Désolé, mes talents
GIMPiens sont encore limités).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/rtfv_m.png&quot; alt=&quot;Read The F****** Vidal&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Plus sérieusement, le fait de partir de Lateral Thoughts, société à
laquelle j’étais associé et où je disposais d’une grande autonomie, peut
poser question. Lateral Thoughts, pour toute personne souhaitant devenir
freelance, est un endroit idéal. On peut même y être salarié en ayant
les mêmes avantages (rémunérations nets moindres, évidemment). Oui, mais
voilà, alors que le freelancing fait rage depuis plusieurs années dans
notre &quot;industrie&quot;, ma voie actuelle s’en écarte.&lt;/p&gt;

&lt;h1 id=&quot;le-déclencheur&quot;&gt;Le déclencheur&lt;/h1&gt;

&lt;p&gt;Il y a quelques mois, j’ai été contacté par un recruteur Google.  De
l’agréable surprise s’ensuivit un stress énorme et des préparations
d’entretien jusqu’à la dernière marche courant Juin : la journée
d’entretiens à Paris. Finalement non retenu à l’ultime jury de sélection
de cette ultime étape, je n’en retiens que du positif. Petite
parenthèse, quand je vois certains critiquer les entretiens où il est
demandé de coder, je rigole doucement. Tentez le marathon Google et on
en reparle :)
Revenons à nos moutons. 
Comme je le disais, cette expérience intense m’a énormément appris : la
lecture des publications de Google, entr’apercevoir l’entreprise pendant
quelques heures, parler avec quelques ingénieurs… ont renforcé ma
conviction sur un point : je veux être développeur, et rien d’autre.
C’est un peu l’essence de notre métier, tel que je le conçois, qui m’est
revenu en pleine figure : la technique au service du besoin. Et quand je
dis technique, je ne parle pas du dernier framework à la mode ou du
dernier &lt;a href=&quot;https://developer.apple.com/swift/&quot;&gt;langage&lt;/a&gt; soi-disant
révolutionnaire. Je pense plutôt à de l’algorithmie, du design (pas
celui de l’Architecte Omniscient, hein). Les ingés de Google n’ont pas
créé &lt;a href=&quot;http://cracking8hacking.com/cracking-hacking/Ebooks/Misc/pdf/The%20Google%20filesystem.pdf&quot;&gt;Google
FileSystem&lt;/a&gt;
pour le fun ou pour en parler en conférence, mais bien parce que le
besoin était criant. Revenir aux fondamentaux a donc redynamisé mon
intérêt pour le développement et m’a fait prendre conscience de la
distance entre mon quotidien, le microcosme dans lequel j’évolue et le
quotidien présenté dans une entreprise d’une telle ampleur.&lt;/p&gt;

&lt;h1 id=&quot;et-pourquoi-pas-freelance-&quot;&gt;Et pourquoi pas freelance ?&lt;/h1&gt;

&lt;p&gt;Le fait d’évoluer en quasi-freelance m’a appris beaucoup de choses. Ça
pourrait en fait se résumer en une phrase : on n’obtient que ce que l’on
va chercher. 
Une bonne mission ? Trouve-la toi-même (ou fais en sorte que celle où tu
es le devienne).
Pas content de telle ou telle situation ? Agis ou accepte.
Tout n’est pas rose non plus.
Au sein d’un regroupement de freelances ou simili-freelances comme à
Lateral Thoughts, chacun, et c’est bien normal, trace son bonhomme de
chemin et fait émerger les projets qu’il a envie de développer. Là où
cela se complique, c’est quand il s’agit de mutualiser les efforts. Pas
de magie : si tu as besoin de plus de cerveaux pour co-réaliser ton
idée, il faut convaincre. 
C’est un procédé juste, mais usant voire parfois démotivant.
Pour qui me donne-je du mal ? Pour ma personne ? Pour Lateral Thoughts
?
Une des réponses est : “en t’exposant au public, tu bénéficies de plus
de visibilité et c’est aussi tout bénef’ pour LT”. J’ai d’ailleurs suivi
ce précepte pendant 2 ans, autour de Neo4j, notamment : de Paris à
Istanbul en passant par Genève. 
Enrichissant, mais fatigant aussi.
Finalement, ces entretiens pour Google m’ont redonné un objectif qui
dépasse mon nombril. J’ai touché de près à l’un des géants du Web, une
boîte qui (me) fait rêver et à laquelle j’ai envie de contribuer. 
(J’assume mon côté bisounours).
Bref, Google m’a juste aiguillé sur le bon chemin. Et ce chemin ne passe
pas par le freelancing.&lt;/p&gt;

&lt;h1 id=&quot;larrivée-à-vidal&quot;&gt;L’arrivée à Vidal&lt;/h1&gt;

&lt;p&gt;J’étais déjà intervenu à Vidal et j’y connaissais ses challenges
techniques. L’environnement de travail de notre équipe auto-organisée
est propice à l’amélioration continue et je compte bien l’utiliser à bon
escient. Ce qui m’a motivé pour les rejoindre en tant qu’interne : c’est
la perspective de pouvoir se focaliser sur ce que l’on fait de mieux et
devenir irréprochables (par ordre d’importance) :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;s’approprier nos softs, de leur création au suivi de prod en passant
par les tests&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;devenir de plus en plus véloces sur la maintenance de ces produits&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;oser tenter des choix à contre-courant&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ce ne sont pas les idées qui manquent, ni la motivation générale. J’ai
vraiment à coeur que notre équipe &quot;Software&quot; s’améliore
collectivement.
Nicolas Martignole parlait de l’équipe &lt;a href=&quot;http://www.touilleur-express.fr/2010/03/19/rencontre-avec-des-developpeurs-chez-vidal-software/&quot;&gt;&quot;Software&quot; de Vidal en
2010&lt;/a&gt;,
vivement 2015 !&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">Mais pourquoi ?!</summary></entry><entry><title type="html">Créer une application java avec Neo4j embarqué</title><link href="https://fbiville.github.io/2014/06/17/Creer-une-application-Java-avec-Neo4j-embarque.html" rel="alternate" type="text/html" title="Créer une application java avec Neo4j embarqué" /><published>2014-06-17T00:00:00+00:00</published><updated>2014-06-17T00:00:00+00:00</updated><id>https://fbiville.github.io/2014/06/17/Creer-une-application-Java-avec-Neo4j-embarque</id><content type="html" xml:base="https://fbiville.github.io/2014/06/17/Creer-une-application-Java-avec-Neo4j-embarque.html">&lt;h1 id=&quot;un-long-discours-&quot;&gt;Un long discours ?&lt;/h1&gt;

&lt;p&gt;Après vous avoir assommé avec &lt;a href=&quot;/?post/2014/06/09/Neo4j-sous-le-capot&quot;&gt;mon article
précédent&lt;/a&gt; sur le stockage
interne de Neo4j et sa scalabilité, je vais aujourd’hui me contenter
d’assez peu. En effet, plutôt que de consacrer un effort important à
expliquer des bonnes pratiques autour de la mise en oeuvre de Neo4j dans
des projets Java, pourquoi ne pas créer l’&lt;a href=&quot;https://github.com/fbiville/maven-embedded-neo4j-archetype&quot;&gt;archetype
Maven&lt;/a&gt; qui
fait le boulot ?&lt;/p&gt;

&lt;h1 id=&quot;archetype-maven-&quot;&gt;Archetype…​ Maven ?&lt;/h1&gt;

&lt;p&gt;Alors oui, je sais, certains d’entre vous ne peuvent pas voir Maven en
couleurs. &lt;/p&gt;

&lt;p&gt;Je sais qu’il existe quelques archetypes bien particuliers autour de
Neo4j pour d’autres outils de build tels que
&lt;a href=&quot;https://github.com/sarmbruster/unmanaged-extension-archetype&quot;&gt;celui&lt;/a&gt; de
&lt;a href=&quot;https://twitter.com/darthvader42&quot;&gt;Stefan Armbruster&lt;/a&gt; pour
&lt;a href=&quot;http://www.gradle.org/&quot;&gt;Gradle&lt;/a&gt;. Néanmoins, je n’ai pas croisé
d’archetypes équivalents à celui que je vais vous présenter.&lt;/p&gt;

&lt;p&gt;Si vous pensez en avoir trouvé un, n’hésitez pas à &lt;a href=&quot;https://www.twitter.com/fbiville&quot;&gt;me
contacter&lt;/a&gt; que je le liste ici.&lt;/p&gt;

&lt;h2 id=&quot;physiologie&quot;&gt;Physiologie&lt;/h2&gt;

&lt;p&gt;Penchons-nous maintenant
sur l’&lt;a href=&quot;https://github.com/fbiville/maven-embedded-neo4j-archetype&quot;&gt;archetype&lt;/a&gt; créé
pour l’occasion.&lt;/p&gt;

&lt;p&gt;Il génère des projets embarquant :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;neo4j&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;neo4j-kernel (classifier test-jar) pour les tests d’intégration&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;junit&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;assertj-core&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;http://joel-costigliola.github.io/assertj/assertj-neo4j.html&quot;&gt;assertj-neo4j&lt;/a&gt;
n’est pas encore assez mature, je vais tâcher de le faire évoluer avant
de le proposer via l’archetype.&lt;/p&gt;

&lt;h2 id=&quot;contenu&quot;&gt;Contenu&lt;/h2&gt;

&lt;p&gt;Si vous suivez &lt;a href=&quot;https://github.com/fbiville/maven-embedded-neo4j-archetype/blob/master/README.md&quot;&gt;les
instructions&lt;/a&gt;,
vous vous retrouverez avec un projet tout simple : * qui insère des
données avec
&lt;a href=&quot;http://docs.neo4j.org/chunked/stable/cypher-query-lang.html&quot;&gt;Cypher&lt;/a&gt; :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;qui lit des données via le &lt;a href=&quot;http://docs.neo4j.org/chunked/stable/tutorial-traversal-java-api.html&quot;&gt;framework de traversée
Java&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;qui utilise EmbeddedDatabaseRule pour les tests
&lt;a href=&quot;http://junit.org/&quot;&gt;JUnit&lt;/a&gt; (cette &lt;a href=&quot;https://github.com/junit-team/junit/wiki/Rules&quot;&gt;règle
JUnit&lt;/a&gt; encapsule
l’utilisation de Neo4j pour les tests d’intégration via son
&lt;a href=&quot;http://docs.neo4j.org/chunked/stable/tutorials-java-unit-testing.html&quot;&gt;implémentation
spécifique&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Un autre archetype Maven devrait suivre pour l’interfaçage REST de
Neo4j.  L’archetype décrit ici sera bientôt releasé sur Maven Central.
En attendant, vous pouvez déjà l’utiliser et démarrer avec Neo4j sur des
bases saines !&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">Un long discours ?</summary></entry><entry><title type="html">Neo4j Sous Le Capot</title><link href="https://fbiville.github.io/2014/06/09/Neo4j-sous-le-capot.html" rel="alternate" type="text/html" title="Neo4j Sous Le Capot" /><published>2014-06-09T00:00:00+00:00</published><updated>2014-06-09T00:00:00+00:00</updated><id>https://fbiville.github.io/2014/06/09/Neo4j-sous-le-capot</id><content type="html" xml:base="https://fbiville.github.io/2014/06/09/Neo4j-sous-le-capot.html">&lt;h1 id=&quot;3615-ma-vie&quot;&gt;3615-ma-vie&lt;/h1&gt;

&lt;dl&gt;
  &lt;dt&gt;Tout ce qui va suivre n’est qu’un tissu de mauvaises excuses, me&lt;/dt&gt;
  &lt;dt&gt;direz-vous, mais j’ai tout de même quelques circonstances atténuantes&lt;/dt&gt;
  &lt;dt&gt;quant à l’inactivité de mon blog (et mon absence de la scène parisienne&lt;/dt&gt;
  &lt;dd&gt;je n’y ai pas fait de talks depuis 6 mois).&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Sur un plan personnel d’abord, je suis heureux de vous annoncer qu’une
jolie alliance orne désormais l’annulaire de ma main gauche :-)&lt;/p&gt;

&lt;p&gt;Sur un plan professionnel, bien qu’absent “publiquement”, beaucoup de
choses se sont passées : ma première &lt;a href=&quot;http://www.lateral-thoughts.com/formation-neo4j&quot;&gt;formation sur
Neo4j&lt;/a&gt; a eu lieu, j’ai
eu l’occasion d’intervenir chez plus de clients et certains projets
autour de Neo4j s’esquissent encore (stay tuned!).&lt;/p&gt;

&lt;p&gt;D’ailleurs, si vous voulez que je vienne parler de Neo4j dans votre User
Group, n’hésitez pas à me contacter (sur
&lt;a href=&quot;https://twitter.com/fbiville&quot;&gt;Twitter&lt;/a&gt; par exemple).&lt;/p&gt;

&lt;h1 id=&quot;back-to-business--parlons-de-neo&quot;&gt;Back to business : parlons de Neo&lt;/h1&gt;

&lt;h2 id=&quot;base-de-données-orientée-graphe-&quot;&gt;Base de données orientée graphe ?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.neo4j.org/&quot;&gt;Neo4j&lt;/a&gt;, vous l’aurez compris, est une base de
données orientée graphe. Mais qu’est-ce qu’“orientée graphe” signifie
exactement ?&lt;/p&gt;

&lt;p&gt;Si l’on cite
&lt;a href=&quot;http://fr.wikipedia.org/wiki/Base_de_donn%C3%A9es_orient%C3%A9e_graphe&quot;&gt;Wikipedia&lt;/a&gt;,
une base de données orientée graphe (&lt;em&gt;graph database&lt;/em&gt;) est donc une base
de données mettant en oeuvre des noeuds, relations et propriétés pour
représenter et stocker de la donnée.&lt;/p&gt;

&lt;p&gt;Cette définition peut vous paraître anodine, mais notez bien la présence
de deux verbes (et non pas d’un seul) : &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;représenter&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;stocker&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En termes plus techniques, une base de données orientée graphe offre
donc une API (“représenter”) exposant un vocabulaire propre au graphe.
Ses enregistrements sur disque (“stocker”) doivent eux aussi être
formatés selon les structures d’un graphe.&lt;/p&gt;

&lt;p&gt;Ce deuxième point est fondamental. &lt;/p&gt;

&lt;p&gt;Prenons l’exemple d’un concurrent de Neo4j :
&lt;a href=&quot;http://thinkaurelius.github.io/titan/&quot;&gt;Titan&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Dès la page d’accueil, on peut lire : &lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Titan is a scalable graph database […​] &lt;/p&gt;

  &lt;p&gt;Support for various storage backends:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;Apache Cassandra&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Apache HBase&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Oracle BerkeleyDB&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Akiban Persistit&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Cela contredit la définition que je vous ai donnée plus haut. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si Titan était une base de données graphe, cela impliquerait que
Cassandra, HBase, BerkeleyDB et Persistit le soient. Or, jusqu’à preuve
du contraire, cela n’est pas le cas :)&lt;/p&gt;

&lt;p&gt;Titan propose une &lt;strong&gt;surcouche&lt;/strong&gt; d’API orientée graphe, déléguant la
persistance à des stores distribuées. Cela n’en fait pas pour autant une
base de données orientée graphe, tout comme &lt;a href=&quot;https://giraph.apache.org/&quot;&gt;Apache
Giraph&lt;/a&gt; n’est “qu’une” API de calcul
orientée graphe.&lt;/p&gt;

&lt;p&gt;“Quelle importance ?”, me direz-vous ?&lt;/p&gt;

&lt;p&gt;Hé bien, une base de données graphe, bien qu’elle offre des nombreux
avantages, est intrinsèquement difficile à distribuer comme nous allons
le voir au travers de cet article. C’est en regardant les couches les
plus basses d’une base typiquement orientée graphe comme Neo4j que vous
allez comprendre ce qu’être une base de données graphe implique en
termes de partis pris.&lt;/p&gt;

&lt;h2 id=&quot;des-liens-et-des-chaînes&quot;&gt;Des liens et des chaînes&lt;/h2&gt;

&lt;p&gt;Neo4j, selon le modèle du &lt;a href=&quot;https://github.com/tinkerpop/blueprints/wiki/Property-Graph-Model&quot;&gt;Property
Graph&lt;/a&gt;,
structure les données par des noeuds liés par des relations. &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Chacune de ces entités peut se voir attribuer un ensemble de
propriétés (une clef [String], une valeur [entier, String,
tableau de primitifs]).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Chaque relation porte obligatoirement une notion de type (exemple :
une relation “FOLLOWS” ou “IS_FRIEND_WITH”).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Chaque noeud porte, depuis la version 2.0, une notion optionnelle
(mais fortement recommandée) appelée “label” (un noeud a de 0 à n
labels).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Évidemment, toutes ces informations sont persistées sur disque.&lt;/p&gt;

&lt;p&gt;Un simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls /path/to/neo/data/graph.db&lt;/code&gt; vous permettra de
constater, outre les fichiers d’indexes Lucene (legacy: répertoire
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index&lt;/code&gt;, nouveau: répertoire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema&lt;/code&gt;) et les journaux de
transactions, les différents fichiers .db :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.labeltokenstore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.nodestore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.propertystore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.relationshipstore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.schemastore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ils représentent tous un “store” dédié à un type de données particulier.
Passons-les en revue individuellement, en commençant par les
nouveautés. &lt;/p&gt;

&lt;p&gt;Notez que les informations à venir sont sujettes à caution : les
&lt;a href=&quot;http://neo4j.com/blog/the-neo4j-2-1-0-milestone-1-release-import-and-dense-nodes/&quot;&gt;récents
travaux&lt;/a&gt;
autour des noeuds denses ont sans doute influencé le format des fichiers
décrits.&lt;/p&gt;

&lt;h3 id=&quot;labeltokenstore&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LabelTokenStore&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;On s’en douterait presque, ce(s) fichier(s) contien(nen)t les
enregistrements de labels. Il(s) n’existai(en)t donc pas avant la sortie
de la 2.0.&lt;/p&gt;

&lt;p&gt;Ces enregistrements comprennent :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;un ID interne (typé int en Java, donc jusqu’à 2³¹ - 1 [sauf Java 8
où on peut avoir des int de 0 à 232 - 1 mais je diverge]). chacun
de ces IDs est référencé dans le fichier
neostore.labeltokenstore.db.id. &lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;et un nom (c’est justement la valeur que vous assignez au label :
“Personne” pour le label Personne) lui-même uniquement identifié
(neostore.labeltokenstore.db.names.id) et stocké dans
(neostore.labeltokenstore.db.names)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ainsi le fichier neostore.labeltokenstore.db ne comporte en fait que des
références vers les IDs internes et noms, stockés “à côté”. Notez que
cette division en fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.db.*&lt;/code&gt; se retrouve pour tous les
autres stores. &lt;/p&gt;

&lt;h3 id=&quot;schemastore&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SchemaStore&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Avec l’émergence des labels est apparu la notion de schema. Ne vous
emballez pas : Neo4j n’est pas devenue une base de données normalisée.
On parle plutôt d’une base de données &lt;em&gt;schema-optional&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Les labels permettent de grouper des noeuds sémantiquement similaires
(cela est donc complètement dépendant du domaine métier) mais rien
n’empêche lesdits noeuds d’être complètement hétérogènes. Par exemple,
deux noeuds peuvent partager le label Personne tout en comportant des
propriétés différentes, disons, la couleur des cheveux pour l’un, la
pointure pour l’autre.&lt;/p&gt;

&lt;p&gt;Maintenant que nous avons des labels à disposition, nous pouvons même
définir des contraintes sur ceux-ci : des contraintes d’unicité par
exemple. Ces contraintes sont en fait appelées &lt;em&gt;rules&lt;/em&gt; et l’ensemble de
celles-ci forment le fameux schema dont je vous parlais. Ce support est
assez récent et la structuration sous-jacente est encore toute simple.
En effet, une rule comprend :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;un ID interne (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.schemastore.db.id&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;sa description à proprement parler (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.schemastore.db&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jusqu’ici, j’ai couvert les additions récentes de Neo4j. &lt;/p&gt;

&lt;p&gt;Bien entendu, Neo n’a pas attendu sa version 2.0 pour être une base de
données orientée graphe à part entière. Regardons ses composants
centraux.&lt;/p&gt;

&lt;h3 id=&quot;propertystore&quot;&gt;PropertyStore&lt;/h3&gt;

&lt;p&gt;À quoi servirait une base de données orientée graphe sans propriétés sur
nos noeuds et relations ? Pas grand chose :-)&lt;/p&gt;

&lt;p&gt;Ces propriétés (rappel : propriété = clef/valeur) néanmoins ne sont pas
enregistrées exactement au même endroit selon certains critères :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.propertystore.db.index&lt;/code&gt; stocke la partie “clef” des
propriétés&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.propertystore.db.arrays&lt;/code&gt;, comme son nom l’indique, est
dédié aux propriétés dont la valeur est un tableau de primitives ou
String&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.propertystore.db.strings&lt;/code&gt; quant à lui se charge de
répertorier les propriétés dont la valeur est une chaîne de
caractères&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;les autres propriétés (booléen, entier) sont stockés directement
dans &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.propertystore.db&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaque jeu de propriétés est propre à la relation/le noeud le contenant,
les propriétés sont représentées comme des listes simplement chaînées.&lt;/p&gt;

&lt;h3 id=&quot;nodestore-et-relationshipstore&quot;&gt;NodeStore et RelationshipStore&lt;/h3&gt;

&lt;p&gt;Le voilà, le nerf de la guerre !&lt;/p&gt;

&lt;p&gt;Commençons par les noeuds. Chaque noeud est composé d’un :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;ID “interne” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.nodestore.db.id&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;des références à ses labels (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.nodestore.db.labels{,.id}&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;une référence vers sa première propriété (l’ID interne de la
propriété) et le premier noeud parmi tous ceux qui lui sont liés (le
tout dans &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.nodestore.db&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptuellement, cela pourrait se représenter ainsi (slide
outrageusement et à de nombreuses reprises emprunté à Neo Technology) : &lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/graph_on_disk.png&quot; alt=&quot;graph on disk&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Tout repose sur la structuration des enregistrements de relations. Cela
est plutôt intuitif : les relations sont l’épine dorsale du graphe.&lt;/p&gt;

&lt;p&gt;Cet élément central se décompose de la façon suivante :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;un ID “interne” (comme d’hab’ : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.relationshipstore.db.id&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;son type (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neostore.relationshiptypestore.db.names&lt;/code&gt;)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour l’instant, ça n’explique pas ce qui en fait une base orientée
graphe. &lt;/p&gt;

&lt;p&gt;Pour cela, regardons plutôt le code Java (eh oui, c’est ça qui est cool
avec les &lt;a href=&quot;https://github.com/neo4j/neo4j&quot;&gt;projets open source&lt;/a&gt; dans les
langages qu’on connaît bien) : &lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.java}&quot;&gt;public class RelationshipRecord extends PrimitiveRecord

{

    private long firstNode;

    private long secondNode;

    private int type;

    private long firstPrevRel = 1;

    private long firstNextRel = Record.NO_NEXT_RELATIONSHIP.intValue();

    private long secondPrevRel = 1;

    private long secondNextRel = Record.NO_NEXT_RELATIONSHIP.intValue();

    // [...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Passons sur le formatage digne des codeurs C les plus chevronnés (qui
pour une Pull Request pour remettre les accolades en fin de ligne ? :P).&lt;/p&gt;

&lt;p&gt;Ce qui est vraiment intéressant ici, c’est cette notion de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;first&lt;/code&gt; et
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;second&lt;/code&gt;. En réalité, il s’agit des références internes (tout est
référence à ce niveau) aux enregistrements correspondant aux noeuds de
départ et d’arrivée. Seulement, la notion de direction n’ayant de sens
qu’au moment du requêtage et non à la création de la relation, on ne
peut pas savoir, à ce niveau, qui du &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;first&lt;/code&gt; ou du &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;second&lt;/code&gt; est le noeud
de départ d’où cette nomenclature.&lt;/p&gt;

&lt;p&gt;Ce que vous devez comprendre de ce petit bout de code, c’est qu’une
relation porte en réalité, outre les informations précédemment
mentionnées :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;une référence vers ses noeuds de départ et d’arrivée&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;une référence vers la précédente relation des noeuds de départ /
d’arrivée&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;une référence vers la relation suivante des noeuds de départ /
d’arrivée&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Une illustration vaut mieux qu’un long discours :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/graph_on_disk_bis.png&quot; alt=&quot;graph on disk bis&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Il s’agit exactement de ce que j’ai tenté d’expliquer : les flèches
rouges symbolisent les liens portés par les enregistrements de
relations. Chacune de ces relations pointe vers les relations
précédentes/suivantes de ses noeuds de départ et d’arrivée.&lt;/p&gt;

&lt;p&gt;Autrement dit, chaque noeud référence (flèche verte) un élément d’une
liste doublement chaînée de relations.&lt;/p&gt;

&lt;p&gt;Et c’est là la nature même du graphe !&lt;/p&gt;

&lt;p&gt;C’est par cette structure que Neo4j peut se targuer d’être une base de
données graphe.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Comment requêter de la donnée dans un graphe ? Par une traversée.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Comment traverser dans Neo4j ? En trouvant les points de départ les
plus pertinents possible et en naviguant dans listes de
relations/noeuds.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vous commencez à comprendre pourquoi ce genre de base de données
s’adapte très bien aux données fortement connectées ?&lt;/p&gt;

&lt;h3 id=&quot;quid-des-noeuds-denses-&quot;&gt;Quid des noeuds denses ?&lt;/h3&gt;

&lt;p&gt;Ahah, je vois que j’ai affaire à des lecteurs initiés ;)&lt;/p&gt;

&lt;p&gt;Resituons le contexte au travers de deux situations légèrement
différentes.&lt;/p&gt;

&lt;h4 id=&quot;situation-n1&quot;&gt;Situation n°1&lt;/h4&gt;

&lt;p&gt;Un noeud dense est un noeud qui est fortement connecté. De nombreux
exemples se retrouvent d’ailleurs dans la vie courante. Par exemple,
Justin Bieber a 52 millions de followers sur Twitter (tiens, je ne
savais pas que la surdité était devenu un phénomène de masse).&lt;/p&gt;

&lt;p&gt;Rappelez-vous, le noeud Justin Bieber pointe vers sa première relation.
Si par manque de chance, vous avez besoin d’accéder à son 52 millionième
noeud-fan, vous allez devoir traverser, dans le pire des cas,
l’intégralité de la liste doublement chaînée des relations avant de le
retrouver : bref, du O(n)…​ vraiment pas terrible.&lt;/p&gt;

&lt;p&gt;Ceci dit, ce cas reste relativement rare. Modifions légèrement
l’exemple.&lt;/p&gt;

&lt;h4 id=&quot;situation-n2&quot;&gt;Situation n°2&lt;/h4&gt;

&lt;p&gt;Justin Bieber a certes 52 millions de followers mais il a bien moins de
personnes dans sa famille.&lt;/p&gt;

&lt;p&gt;Si par hasard, parmi cette gigantesque quantité de relations, seules les
relations familiales vous intéressent, vous faites face exactement au
même problème que décrit ci-dessus… si vous utilisez une version de
Neo4j antérieure à la version 2.1 de Neo4j. &lt;/p&gt;

&lt;p&gt;Depuis cette version, les relations sont aussi discriminées par type,
permettant ainsi de ne pas tomber dans cet écueuil. Un noeud est
d’ailleurs considéré dense à partir de 50 relations par défaut (cf.
“http://docs.neo4j.org/chunked/stable/kernel-configuration.html[dense
node threshold]”).&lt;/p&gt;

&lt;h4 id=&quot;help-je-suis-dans-la-situation-n1&quot;&gt;Help! Je suis dans la situation n°1!&lt;/h4&gt;

&lt;p&gt;Si par malheur, et après exploration de toutes les alternatives
(échantillonnage statistique etc), vous en concluez que vous ne pouvez
faire autrement : rassurez-vous !&lt;/p&gt;

&lt;p&gt;Tout d’abord, les équipes de Neo continuent de plancher et d’apporter
des améliorations à ce sujet. Nous devrions donc voir quelques
améliorations avec la v2.2.&lt;/p&gt;

&lt;p&gt;De plus, une approche simple &lt;a href=&quot;https://github.com/maxdemarzi/dense&quot;&gt;est déjà codée pour
vous&lt;/a&gt; par l’excellent
&lt;a href=&quot;https://twitter.com/maxdemarzi&quot;&gt;Max&lt;/a&gt; &lt;a href=&quot;http://maxdemarzi.com/&quot;&gt;de&lt;/a&gt;
&lt;a href=&quot;https://www.kickstarter.com/projects/1355751798/high-performance-neo4j-video-course&quot;&gt;Marzi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;L’idée de son extension est simple : elle va simplement ventiler les
noeuds par niveau lors de chaque nouvelle insertion et les lire de façon
transparente.&lt;/p&gt;

&lt;p&gt;Voici donc un exemple de structure automatiquement créée par son
extension :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/dense_nodes.png&quot; alt=&quot;dense nodes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Tout comme Justin Bieber, Lady Gaga et Madonna ont également de nombreux
fans (chaque fan “LIKES” l’artiste). Un noeud factice va donc se
substituer aux noeuds que l’on aurait directement lié aux artistes et
introduire des couches, par le biais de noeuds intermédiaires regroupant
eux aussi un nombre limité de fans, relié alors par une “DENSE_LIKES”.
Les relations sont maintenant réparties et l’on pourra paginer nos
requêtes de lecture de cette façon : &lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-{.cypher}&quot;&gt;MATCH (fan:Fan)-[:DENSE_LIKES*0..5]-&amp;gt;()-[:LIKES]-&amp;gt;(loved:Artist {name:
“Madonna”})
RETURN fan
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Cette requête signifie (en lisant le pattern de bas en haut, de droite à
gauche) :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;retourne tous les noeuds au label “Artist” et au nom “Madonna” +
qui sont “LIKÉS“ par un noeud quelconque (appelons-le META) +
et 0 à 5 relations DENSE_LIKE séparent META des noeuds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Étant donné que la requête recherche les nombreux fans d’un artiste,
sans aucune ventilation du graphe, nous serions en plein dans la
situation n°1 décrite préalablement. Néanmoins, cette approche simple
couplée à l’usage astucieux des &lt;a href=&quot;http://docs.neo4j.org/chunked/milestone/query-match.html#match-variable-length-relationships&quot;&gt;variable-length
paths&lt;/a&gt;
permet de ne récupérer qu’une fraction des fans sans pour autant
traverser toutes les relations dont l’artiste dépend.&lt;/p&gt;

&lt;h2 id=&quot;neo4j-et-scalabilité&quot;&gt;Neo4j et scalabilité&lt;/h2&gt;

&lt;p&gt;Maintenant que le format physique des fichiers est un peu plus clair,
regardons un peu les couches supérieures.&lt;/p&gt;

&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;

&lt;p&gt;Les accès disques sont bien évidemment limités autant que possible. Deux
niveaux de cache interviennent.&lt;/p&gt;

&lt;h4 id=&quot;le-file-buffer-cache&quot;&gt;Le &lt;em&gt;file buffer cache&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;Vous vous en doutez, le file buffer cache sert de tampon aux
écritures/lectures des enregistrements physiques (cf. les fichiers
décrits précédemment). Les entrées les moins récemment accédées sont
évincées du buffer
(&lt;a href=&quot;http://en.wikipedia.org/wiki/Least_Recently_Used#LRU&quot;&gt;LRU&lt;/a&gt;). Si
possible, ce buffer est directement mappé au fichier store sous-jacent
(“memory-mapping”). Ce comportement dépend du système de fichiers et de
l’OS.  Quoi qu’il en soit, cette couche a pour seul but de réduire au
maximum les accès disque mais n’introduit aucune forme d’abstraction sur
les données manipulées.&lt;/p&gt;

&lt;h4 id=&quot;lobject-cache&quot;&gt;L’&lt;em&gt;object cache&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;Lui aussi cache LRU, c’est à partir de ce moment-là que les données
manipulées commencent à prendre la forme du graphe que vous requêtez par
traversée ou par Cypher. Notez que l’allocation mémoire à ce niveau est
prise sur la heap de la JVM hôte et non plus directement de l’OS hôte
sous-jacent. C’est pourquoi il est souvent préférable de déployer Neo4j
de façon isolée, afin que votre application ne vienne pas perturber
(comme par exemple : ) les cycles GC de votre instance Neo et
vise-versa.&lt;/p&gt;

&lt;h4 id=&quot;et-le-reste&quot;&gt;et le reste&lt;/h4&gt;

&lt;p&gt;À partir de là, les APIs unitaires Java prennent le relais, suivies des
APIs de traversées, Cypher et les APIs REST !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/neo4j_archi.png&quot; alt=&quot;neo4j archi&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;gestion-de-la-concurrence&quot;&gt;Gestion de la concurrence&lt;/h3&gt;

&lt;p&gt;Bien que faisant partie de cette (non-)famille qu’est NoSQL, Neo4j fait
un peu figure d’exception, en se conformant à ACID. En effet, vous
retrouverez avec Neo4j les transactions en 2 phases que vous connaissez
bien. N’étant pas un spécialiste des systèmes distribués, je vous invite
à lire la multitude d’articles existants sur les limites d’ACID, les
limites du locking et les alternatives existantes (“lock-free
concurrency”, BASE vs ACID) : Google est votre ami. J’en profite donc
pour passer à la partie qui m’intéresse le plus : le &lt;em&gt;sharding&lt;/em&gt; :)&lt;/p&gt;

&lt;h3 id=&quot;sharding-dun-graphe-dynamique&quot;&gt;&lt;em&gt;Sharding&lt;/em&gt; d’un graphe dynamique&lt;/h3&gt;

&lt;p&gt;Expliquons brièvement le terme &lt;em&gt;sharding&lt;/em&gt;. Le &lt;em&gt;sharding&lt;/em&gt; consiste
simplement à répartir ses données entre différentes instances d’un
système de persistence distribué. Par exemple : je peux décider de
stocker toutes les adresses postales américaines sur mes serveurs aux
États-Unis et mes adresses australiennes à Sydney. Une instance donnée
ne contient donc pas l’intégralité des données, mais le domaine métier
auquel appartient mon application appartient comporte des notions qui se
répartissent naturellement. Eh oui ! Le &lt;em&gt;sharding&lt;/em&gt; est une solution
technique, certes, mais hautement dépendante du métier (comme toute
solution technique devrait l’être, mais je digresse).&lt;/p&gt;

&lt;h4 id=&quot;graphe-statique&quot;&gt;Graphe statique&lt;/h4&gt;

&lt;p&gt;Un graphe statique est plutôt facile à &lt;em&gt;sharder&lt;/em&gt; (dans la mesure où le
domaine métier modélisé le permet), ses fragmentations sont faciles à
détecter (on parle de “&lt;em&gt;graph clustering&lt;/em&gt;” ou de “&lt;em&gt;community
detection&lt;/em&gt;”) : elles ne sont pas amenées à évoluer du tout.  &lt;a href=&quot;http://en.wikipedia.org/wiki/Strongly_connected_component&quot;&gt;Certains
algorithmes&lt;/a&gt;
sont même relativement faciles à implémenter.&lt;/p&gt;

&lt;h4 id=&quot;graphe-dynamique&quot;&gt;Graphe dynamique&lt;/h4&gt;

&lt;p&gt;Pour les graphes dynamiques, en revanche, c’est une autre paire de
manche. De nombreuses opérations d’insertion et suppression
interviennent en permanence et elles impactent nécessairement la
topologie du graphe. Le but du jeu est donc de déterminer un découpage
du graphe en shards de telle sorte, qu’à tout instant, le nombre de
relations inter-shards soit minimisé. Cela est d’autant plus critique
que les shards sont distants (imaginez la latence réseau induite par une
traversée qui commence par un shard hébergé à Los Angeles pour finir
dans un shard à Pékin).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/neo4j_shards.png&quot; alt=&quot;neo4j shards&quot; /&gt;&lt;/p&gt;

&lt;p&gt;C’est un &lt;a href=&quot;http://alexaverbuch.blogspot.fr/2010/04/me-my-names-alex-im-currently.html&quot;&gt;sujet de
recherche&lt;/a&gt;
à part entière et Neo Technology travaille depuis plusieurs années sur
un système shardable. Comprenez bien le terrible dilemne : par son
orientation graphe dès les couches physiques, Neo4j est à la fois idéal
pour stocker et requêter des données sous forme de graphe mais également
très difficile à sharder !&lt;/p&gt;

&lt;h4 id=&quot;une-lueur-despoir-&quot;&gt;Une lueur d’espoir ?&lt;/h4&gt;

&lt;dl&gt;
  &lt;dt&gt;Il est pour l’instant nécessaire de miser sur du [*scaling&lt;/dt&gt;
  &lt;dt&gt;vertical*](http://fr.wikipedia.org/wiki/Scalability) : dimensionnez&lt;/dt&gt;
  &lt;dt&gt;suffisamment vos machines et tout se passera très bien. Laissez-moi vous&lt;/dt&gt;
  &lt;dt&gt;rassurer davantage : * jusqu’à présent, une infime minorité de clients&lt;/dt&gt;
  &lt;dt&gt;a été confrontée à une volumétrie telle ([capacité nomimale de&lt;/dt&gt;
  &lt;dt&gt;Neo4j](http://docs.neo4j.org/chunked/stable/capabilities-capacity.html)&lt;/dt&gt;
  &lt;dd&gt;34 millards de noeuds et de relations) qu’une répartition des données
était nécessaire * il se trouve que certains domaines métiers
permettent naturellement de ségréguer ses données * il existe un début
de solution de répartition !&lt;/dd&gt;
&lt;/dl&gt;

&lt;h4 id=&quot;le-cache-sharding-&quot;&gt;Le &lt;em&gt;cache sharding&lt;/em&gt; !&lt;/h4&gt;

&lt;p&gt;Le titre peut faire peur, mais rassurez-vous, l’idée est toute simple.
Tout d’abord, cette idée s’applique à Neo4j en mode &lt;a href=&quot;http://docs.neo4j.org/chunked/stable/ha-how.html&quot;&gt;High
Availability&lt;/a&gt;. En
d’autres termes, cela ne s’applique qu’à une instance Neo4j au sein
d’un &lt;em&gt;cluster&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Non seulement vous bénéficiez d’une réplication master/replica, mais
vous pouvez également bénéficier de &lt;em&gt;sharding&lt;/em&gt;. Oui, oui, j’ai bien dit
&lt;em&gt;sharding&lt;/em&gt;. Malheureusement, pour les raisons évoquées plus haut, il ne
s’agit pas de &lt;em&gt;sharding&lt;/em&gt; sur les données à proprement parler. Comme le
titre l’évoque, il s’agit de sharding sur le cache.&lt;/p&gt;

&lt;p&gt;Comment est-ce possible ? C’est tout simple !&lt;/p&gt;

&lt;p&gt;Les caches de Neo4j sont des caches LRU, ils ne conservent que les
entrées les plus récentes en leur sein. S’il existait un moyen de
répartir les requêtes de façon persistante entre chaque instance de mon
cluster, le tour serait joué. En effet, la requête X serait toujours
exécutée sur l’instance A, la requête Y sur l’instance B… Le résultat
X serait de facto dans les caches A, celui d’Y dans les caches B. Mes
données seraient donc effectivement réparties par cache. Le problème se
réduit donc à : comment répartir de façon consistante les requêtes à
exécuter entre les instances de mon cluster Neo4j ? Je vous le donne en
mille. La solution existe depuis des lustres : un simple load balancer
comme &lt;a href=&quot;http://haproxy.1wt.eu/&quot;&gt;HAProxy&lt;/a&gt; saura faire l’affaire. On parle
de consistent routing (plus généralement de &lt;a href=&quot;http://en.wikipedia.org/wiki/Consistent_hashing&quot;&gt;&lt;em&gt;consistent
hashing&lt;/em&gt;&lt;/a&gt;).  Il suffit
de configurer sa façon de router selon un des arguments présents dans le
corps ou un quelconque entête des appels HTTP envoyés à Neo
(rappelez-vous : toute communication distante est définie par une API
REST) et le load balancer se chargera d’exécuter vos ordres là où vous
l’avez configuré ! Astucieux, non ? Un simple load balancer, un cluster
Neo4j (l’édition High Availability vous fournit tous les outils qu’il
vous fait) et vous êtes prêts à affronter une forte volumétrie de
données !&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Une des leçons de NOSQL est que toute solution se restreint à un certain
champ d’application et s’applique sous certaines conditions. J’espère
que cet article vous aura permis de comprendre les faiblesses mais
surtout les forces des bases de données graphe et, qui sait, vous
donnera envie d’approfondir le sujet.&lt;/p&gt;

&lt;p&gt;Je ne prétends pas à l’exhaustivité, donc si vous souhaitez que je
détaille d’autres parties (exemple : Cypher), je peux éventuellement y
consacrer d’autres articles.&lt;/p&gt;

&lt;p&gt;&amp;lt;shameless_plug&amp;gt;Si cet article vous a plu, je peux aussi venir en
parler dans un User Group de votre ville et je donne des
formations customisables
sur Neo4j et en français ! &amp;lt;/shameless_plug&amp;gt;&lt;/p&gt;</content><author><name>Florent Biville</name></author><summary type="html">3615-ma-vie</summary></entry></feed>