[1;32m0001 █ [0m~start-paragraph
[1;32m0001 █ [0m~start
[1;32m0001 █ [0m~change [{"type":"put","key":["rpr"]},{"type":"put","key":["urge"]},{"type":"put","key":["help"]},{"type":"put","key":["setImmediate"]},{"type":"put","key":["echo"]},{"type":"put","key":["mkts"],"value":{"output":[],"__filename":"XXXXXXXXXXXXX"}}]
[1;32m0001 █ [0m~flush
[1;32m0001 █ [0m  (document
[1;32m0001 █ [0m  .text '
'
[1;32m0001 █ [0m  !columns [2]
[1;32m0001 █ [0m  .p
[1;32m0001 █ [0m  ~start-paragraph
[1;32m0001 █ [0m  .insert {"src":"./chapter-00-intro.md","mode":"mktscript"}
[1;32m0001 █ [0m  .mktscript '<document/>

## PipeStreams: Pipelines and Streams

### Terminology

* **Sources** (a.k.a. \'readables\') do not take inputs from the stream, but
  provide data to \'flow downstream\'. Sources may be constructed from values
  (e.g. a text may be converted into a source of text lines or a series of
  characters), from files (reading files and processing their contents being a
  frequent application of streams), or using functions that sit around until
  they are called with pieces of data they then send downstream.

* **Sinks** (a.k.a. \'writables\') do not provide outputs to the stream, but
  accept all data that is funnelled to them by way of the pipeline. There are
  two important subclasses of sinks: on the one hand, sinks may be used to write
  to a file, an outgoing HTTP connection, or a database; so that would be sinks
  with targets. Target-less sinks may seem pointless but they are needed to
  provide a writable endpoint to a pipeline. The function used to represent such
  a sink is known as `$drain` in PipeStreams, so \'a drain\' just means that, a
  generic target-less sink required by the API.

* **Pipelines** are lists of **stream transforms**. As we will shortly see,
  pipelines represent the central constructional element in the PipeStreams way
  of doing things. They start out as generic lists (i.e. Javascript `Array`s)
  that transforms—functions—are being inserted to, and they end up representing,
  in the ordering of their elements, the ordering of transformational steps that
  each piece of data fed into their top end has to undergo before coming out at
  the other end. A well-written pipeline combines a simple conceptual model with
  a highly readable list of things to do, with each station along the assembly
  line doing just one specific thing.

* **Transforms** (symbolized as `$f()`) are synchronous or asynchronous
  functions that accept one data item *d*<sub>*i*</sub> (a.k.a. events) from
  upstream and pass zero or more data items *d*<sub>*1*</sub>,
  *d*<sub>*2*</sub>,&nbsp;... down the stream, consecutively.

In short one can say that in each stream, data comes out of a source,
flows through a number of transforms $*f<sub>i</sub>*, and goes into some
kind of sink. A **complete pipeline** has at least a source and a
sink.

* By **streams** (symbolized as Σ) we mean the activity that occurs when a
  complete pipeline has been \'activated\', that is made start to process data.
  So, technically, a \'stream\' (a composite algorithm at work) is what a
  \'pipeline\', once activated, does. In practice, the distinction is often
  blurred, and one can, for example, just as well say that a particular event is
  \'coming down the stream\' or \'coming down the pipeline\'.

### Equivalence Rules

The power of streams—and by streams I mean primarily
[`pull-stream`](http://pull-stream.github.io/)s on which PipeStreams is
built—comes from the abstractions they provide. In programming, *functions* are
such powerful abstractions because when you take a chunk of code and make it a
function with a name and a call signature, all of a sudden you have not only a
piece of code that you can pass around and invoke, you can also put that
invocation into *another* named function and so on. So, a building block made
from smaller building blocks remains a building block, albeit a more complex
one.

Likewise, when building processing pipelines from stream transforms, you start
out with pipelines built from stream primitives, and then you can go and put
entire pipelines into other pipelines, taking advantage of the compositional
powers afforded by the equivalence rules (invariants) that streams guarantee. In
the below, we use an arrow `a -> b` to symbolize \'`a` is equivalent to `b`\',
i.e. \'`b` acts like an `a`\'. `pull` represents the PipeStreams `pull` method
(basically `pull-stream`\'s `pull` method); `$f()`, `$g()` represent stream
transforms (see *API Naming and Conventions* for the leading dollar sign); the
ellipsis notation `$f(), ...` represents \'any number of transforms\'.

* A pipeline with a source and any number of transforms is equivalent to a
  source:

`pull [ source_1, $f(), ..., ] -> source`

* A pipeline with any number of transforms and a sink is equivalent to a sink:

`pull [ $f(), ..., sink_1 ] -> sink`

* A pipeline with any number of transforms is equivalent to a (more complex)
  transform:

`pull [ $f(), ..., ] -> $g()`

* In particular, a pipeline with no elements is equivalent to the empty (no-op)
  transform that passes all data through (and that can be omitted in any
  pipeline except for a smallish penalty in performance):

`pull [] -> PS.$pass()`

* A pipeline with a source, any number of transforms and a sink is equivalent to
  a stream:

`pull [ source, $f(), ..., sink, ] -> stream`


### API Naming and Conventions



`PS.pull pipeline...`

### Simple Examples

**Ex. 1**

```coffee
PS  = require \'pipestreams\'
log = console.log
p   = []
p.push PS.new_value_source [ \'foo\', \'bar\', \'baz\', ]
p.push PS.$show()
p.push PS.$drain -> log \'done\'
PS.pull p...
```

**Ex. 2**

```coffee
PS              = require \'pipestreams\'
{ $, $async, }  = PS

$double = ->
  return $ ( d, send ) -> send 2 * d

source  = PS.new_push_source()
p       = []
p.push source
p.push $double()
p.push PS.$show()
p.push PS.$drain()
PS.pull p...
source.push 42
```




'
[1;32m     █  [0m  tex


{\mktsHTwo{}\zlabel{h-0}PipeStreams: Pipelines and Streams\mktsHTwoBeg}%

{\mktsHThree{}\zlabel{h-1}Terminology\mktsHThreeBeg}%

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Sources} (a.k.a. ‘readables’) do not take inputs from the stream, but
provide data to ‘flow downstream’. Sources may be constructed from values
(e.g. a text may be converted into a source of text lines or a series of
characters), from files (reading files and processing their contents being a
frequent application of streams), or using functions that sit around until
they are called with pieces of data they then send downstream.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Sinks} (a.k.a. ‘writables’) do not provide outputs to the stream, but
accept all data that is funnelled to them by way of the pipeline. There are
two important subclasses of sinks: on the one hand, sinks may be used to write
to a file, an outgoing HTTP connection, or a database; so that would be sinks
with targets. Target-less sinks may seem pointless but they are needed to
provide a writable endpoint to a pipeline. The function used to represent such
a sink is known as {\mktsStyleCode{}\$drain} in PipeStreams, so ‘a drain’ just means that, a
generic target-less sink required by the API.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Pipelines} are lists of {\mktsStyleBold{}stream transforms}. As we will shortly see,
pipelines represent the central constructional element in the PipeStreams way
of doing things. They start out as generic lists (i.e. Javascript {\mktsStyleCode{}Array}s)
that transforms—functions—are being inserted to, and they end up representing,
in the ordering of their elements, the ordering of transformational steps that
each piece of data fed into their top end has to undergo before coming out at
the other end. A well-written pipeline combines a simple conceptual model with
a highly readable list of things to do, with each station along the assembly
line doing just one specific thing.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Transforms} (symbolized as {\mktsStyleCode{}\$f()}) are synchronous or asynchronous
functions that accept one data item {\mktsStyleItalic{}d\/}{\mktsStyleFontSubscript{}{\mktsStyleItalic{}i\/}} (a.k.a. events) from
upstream and pass zero or more data items {\mktsStyleItalic{}d\/}{\mktsStyleFontSubscript{}{\mktsStyleItalic{}1\/}},
{\mktsStyleItalic{}d\/}{\mktsStyleFontSubscript{}{\mktsStyleItalic{}2\/}},~… down the stream, consecutively.


In short one can say that in each stream, data comes out of a source,
flows through a number of transforms \${\mktsStyleItalic{}f\/}{\mktsStyleFontSubscript{}{\mktsStyleItalic{}i\/}}, and goes into some
kind of sink. A {\mktsStyleBold{}complete pipeline} has at least a source and a
sink.

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}By {\mktsStyleBold{}streams} (symbolized as Σ) we mean the activity that occurs when a
complete pipeline has been ‘activated’, that is made start to process data.
So, technically, a ‘stream’ (a composite algorithm at work) is what a
‘pipeline’, once activated, does. In practice, the distinction is often
blurred, and one can, for example, just as well say that a particular event is
‘coming down the stream’ or ‘coming down the pipeline’.



{\mktsHThree{}\zlabel{h-2}Equivalence Rules\mktsHThreeBeg}%

The power of streams—and by streams I mean primarily
{\mktsStyleLinklabel{}{\mktsStyleCode{}pull-\allowbreak{}stream}}{\mktsEnStyleMarkMain{}1}s on which PipeStreams is
built—comes from the abstractions they provide. In programming, {\mktsStyleItalic{}functions\/} are
such powerful abstractions because when you take a chunk of code and make it a
function with a name and a call signature, all of a sudden you have not only a
piece of code that you can pass around and invoke, you can also put that
invocation into {\mktsStyleItalic{}another\/} named function and so on. So, a building block made
from smaller building blocks remains a building block, albeit a more complex
one.

\mktsIndent{}Likewise, when building processing pipelines from stream transforms, you start
out with pipelines built from stream primitives, and then you can go and put
entire pipelines into other pipelines, taking advantage of the compositional
powers afforded by the equivalence rules (invariants) that streams guarantee. In
the below, we use an arrow {\mktsStyleCode{}a \allowbreak{}-\allowbreak{}> \allowbreak{}b} to symbolize ’{\mktsStyleCode{}a} is equivalent to {\mktsStyleCode{}b}',
i.e. ’{\mktsStyleCode{}b} acts like an {\mktsStyleCode{}a}'. {\mktsStyleCode{}pull} represents the PipeStreams {\mktsStyleCode{}pull} method
(basically {\mktsStyleCode{}pull-\allowbreak{}stream}'s {\mktsStyleCode{}pull} method); {\mktsStyleCode{}\$f()}, {\mktsStyleCode{}\$g()} represent stream
transforms (see {\mktsStyleItalic{}API Naming and Conventions\/} for the leading dollar sign); the
ellipsis notation {\mktsStyleCode{}\$f(), \allowbreak{}…} represents ‘any number of transforms’.

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}A pipeline with a source and any number of transforms is equivalent to a
source:


{\mktsStyleCode{}pull \allowbreak{}[ source\_1, \allowbreak{}\$f(), \allowbreak{}…, ] \allowbreak{}-\allowbreak{}> \allowbreak{}source}

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}A pipeline with any number of transforms and a sink is equivalent to a sink:


{\mktsStyleCode{}pull \allowbreak{}[ \$f(), \allowbreak{}…, \allowbreak{}sink\_1 ] \allowbreak{}-\allowbreak{}> \allowbreak{}sink}

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}A pipeline with any number of transforms is equivalent to a (more complex)
transform:


{\mktsStyleCode{}pull \allowbreak{}[ \$f(), \allowbreak{}…, ] \allowbreak{}-\allowbreak{}> \allowbreak{}\$g()}

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}In particular, a pipeline with no elements is equivalent to the empty (no-op)
transform that passes all data through (and that can be omitted in any
pipeline except for a smallish penalty in performance):


{\mktsStyleCode{}pull \allowbreak{}[] \allowbreak{}-\allowbreak{}> \allowbreak{}PS.\allowbreak{}\$pass()}

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}A pipeline with a source, any number of transforms and a sink is equivalent to
a stream:


{\mktsStyleCode{}pull \allowbreak{}[ source, \allowbreak{}\$f(), \allowbreak{}…, \allowbreak{}sink, ] \allowbreak{}-\allowbreak{}> \allowbreak{}stream}


{\mktsHThree{}\zlabel{h-3}API Naming and Conventions\mktsHThreeBeg}%

{\mktsStyleCode{}PS.pull \allowbreak{}pipeline…}


{\mktsHThree{}\zlabel{h-4}Simple Examples\mktsHThreeBeg}%

{\mktsStyleBold{}Ex. 1}

{\mktsTightParagraphs{}

{\mktsStyleCode{}PS  = require 'pipestreams'\par
log = console.log\par
p   = []\par
p.push PS.new\_value\_source [ 'foo', 'bar', 'baz', ]\par
p.push PS.\$show()\par
p.push PS.\$drain -> log 'done'\par
PS.pull p…\par
}

}{\mktsStyleBold{}Ex. 2}

{\mktsTightParagraphs{}

{\mktsStyleCode{}PS              = require 'pipestreams'\par
\{ \$, \$async, \}  = PS\par
\null\par
\$double = ->\par
  return \$ ( d, send ) -> send 2 * d\par
\null\par
source  = PS.new\_push\_source()\par
p       = []\par
p.push source\par
p.push \$double()\par
p.push PS.\$show()\par
p.push PS.\$drain()\par
PS.pull p…\par
source.push 42\par
}

}
[1;32m0001 █ [0m  .text '
'
[1;32m0001 █ [0m  .comment '<'
[1;32m0001 █ [0m  .insert {"src":"./chapter-00-comparison.md","mode":"mktscript"}
[1;32m0001 █ [0m  .mktscript '<document/>
### Comparison with NodeJS Streams, Pull-Streams

Here are a few points that highlight the reasons why I wrote the PipeStreams
library on top of [pull-stream](https://github.com/pull-stream/pull-stream)s
(after writing [PipeDreams](https://github.com/loveencounterflow/pipedreams)
which were built on top of [NodeJS
Streams](https://nodejs.org/api/stream.html)):

* The [basic API ideas of
  PipeDreams](https://github.com/loveencounterflow/pipedreams#the-remit-and-remit-async-methods)
  turned out to be a highly useful and effective tool to create not-so-small
  data processing assemblies. Before pipelines, such assemblies tended to be
  ad-hoc messes of synchronous and asynchronous pieces of code calling each
  other; after pipelines, assemblies could be written as linear sequences of
  named functions.

* The PipeDreams stream transform call convention—where a transform is (produced
  from) a function `( data, send ) ->` that accepts a piece of `data` and a
  `send` method that is used to send data downstream—proved to be the main
  enabling aspect of said library. All of a sudden you could just dump [all that
  is wrong with NodeJS
  streams](http://dominictarr.com/post/145135293917/history-of-streams) and
  forget about all their [Byzantine
  complexities](https://nodejs.org/api/stream.html): just write a function that
  `( data, send ) -> ... send data ...` and bang, you\'re good to go.

* PipeDreams had some downsides, though; apart from some of the *complexities*
  of NodeJS streams that could not be entirely hidden, it also suffered from
  their [*inherently mediocre performance
  characteristics*](https://github.com/loveencounterflow/basic-stream-benchmarks):
  the architecture of NodeJS streams is such that adding a transform to a
  pipeline incurs a non-trivial run-time performance penalty, so much that **the
  performance of NodejS streams pipelines with more than a very few steps will
  be dominated by the number of steps, even if those steps are no-ops**; this at
  least used to be the case at the time when I abandoned NodeJS streams and
  turned to Pull-Streams. The whole idea of streams is to do one little thing at
  a time and have those many little steps co-operate to accomplish a bigger
  goal; an implementation with an unreasonable cost on adding steps ruins that
  picture.

* The underlying implementation of Pull-Streams is [hugely
  simpler](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
  than that of NodeJS streams. To [quote another
  guy](https://github.com/ipfs/js-ipfs/issues/362#issuecomment-237597850) who
  thinks so:

  > pull streams\' # 1 superpower is their simplicity (in the Rich Hickey sense
  > of the word): anyone can write a full pull stream implementation from
  > scratch in a few minutes, from first principles. This is not true of node
  > streams in the slightest. Simplicity brings transparency with it, meaning
  > debugging and reasoning about implementation gets easier.

  So while \'simple\' doesn\'t equal \'easy\' (in the [Rich
  Hickey](https://www.youtube.com/watch?v=rI8tNMsozo0) sense of the word) it\'s
  still true that simpler concepts, a simpler implementation and a simpler API
  are to be preferred over a convoluted implementation (and API) that suffers
  from backward-compatibility pressures and maintains several parallel, mutually
  exclusive and ultimately superfluous modes of operation. In the case of NodeJS
  streams, you have \'new style\' vs \'old style\' mode of operation: A switch that
  is done transparently based on what seemingly unrelated parts of the API you
  employ in what order. Next, you must decide whether you\'re dealing with
  \'objects\' or \'binary\' data, a completely gratuitous difference: it\'s just
  data. Lastly, you can structure your streaming app to do things the
  `.pipe()`ing way, or, alternatively, the `EventEmitter` way—inheriting [all
  that is wrong with the `EventEmitter` API and
  implementation](https://github.com/sindresorhus/emittery#how-is-this-different-than-the-built-in-eventemitter-in-nodejs).
  To top it off, [you still don\'t get proper error handling with NodeJS
  streams](https://stackoverflow.com/a/22389498/7568091).

  > I\'m not saying that using the event handling model to process streams is
  > wrong, I just claim that having NodeJS do both piping and events where
  > either would have sufficed is what contributes to the rather sad performance
  > story they deliver.

**I\'m really sorry that these points amount to what can be perceived as bashing
on the NodeJS folks who have given us the great piece of software that is
NodeJS**. But frankly, as much as I like NodeJS, I nowadays try to stay away
from using the standard library\'s streams and event emitters. Let\'s just say not
everything in the Nodejs stdlib that *could* conceivably be used in userland
software built on that foundation *should* be used.

With that off the chest, let\'s move on to what PipeStreams claims to provide.

[WIP]

* **The `remit` methods, `$()` and `$async()`**

* **Convenience stream transforms**

* **Circular pipelines**

* **Push sources**

* **Bridge to NodeJS streams**

* **An (optional) convention for data events**

* **Tees: diverting into multiple sinks**



'
[1;32m     █  [0m  tex


{\mktsHThree{}\zlabel{h-0}Comparison with NodeJS Streams, Pull-Streams\mktsHThreeBeg}%

Here are a few points that highlight the reasons why I wrote the PipeStreams
library on top of {\mktsStyleLinklabel{}pull-stream}{\mktsEnStyleMarkMain{}1}s
(after writing {\mktsStyleLinklabel{}PipeDreams}{\mktsEnStyleMarkMain{}2}
which were built on top of {\mktsStyleLinklabel{}NodeJS
Streams}{\mktsEnStyleMarkMain{}3}):

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}The {\mktsStyleLinklabel{}basic API ideas of
PipeDreams}{\mktsEnStyleMarkMain{}4}
turned out to be a highly useful and effective tool to create not-so-small
data processing assemblies. Before pipelines, such assemblies tended to be
ad-hoc messes of synchronous and asynchronous pieces of code calling each
other; after pipelines, assemblies could be written as linear sequences of
named functions.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}The PipeDreams stream transform call convention—where a transform is (produced
from) a function {\mktsStyleCode{}( data, \allowbreak{}send ) \allowbreak{}-\allowbreak{}>} that accepts a piece of {\mktsStyleCode{}data} and a
{\mktsStyleCode{}send} method that is used to send data downstream—proved to be the main
enabling aspect of said library. All of a sudden you could just dump {\mktsStyleLinklabel{}all that
is wrong with NodeJS
streams}{\mktsEnStyleMarkMain{}5} and
forget about all their {\mktsStyleLinklabel{}Byzantine
complexities}{\mktsEnStyleMarkMain{}6}: just write a function that
{\mktsStyleCode{}( data, \allowbreak{}send ) \allowbreak{}-\allowbreak{}> \allowbreak{}… \allowbreak{}send \allowbreak{}data \allowbreak{}…} and bang, you’re good to go.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}PipeDreams had some downsides, though; apart from some of the {\mktsStyleItalic{}complexities\/}
of NodeJS streams that could not be entirely hidden, it also suffered from
their {\mktsStyleLinklabel{}{\mktsStyleItalic{}inherently mediocre performance
characteristics\/}}{\mktsEnStyleMarkMain{}7}:
the architecture of NodeJS streams is such that adding a transform to a
pipeline incurs a non-trivial run-time performance penalty, so much that {\mktsStyleBold{}the
performance of NodejS streams pipelines with more than a very few steps will
be dominated by the number of steps, even if those steps are no-ops}; this at
least used to be the case at the time when I abandoned NodeJS streams and
turned to Pull-Streams. The whole idea of streams is to do one little thing at
a time and have those many little steps co-operate to accomplish a bigger
goal; an implementation with an unreasonable cost on adding steps ruins that
picture.


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}The underlying implementation of Pull-Streams is {\mktsStyleLinklabel{}hugely
simpler}{\mktsEnStyleMarkMain{}8}
than that of NodeJS streams. To {\mktsStyleLinklabel{}quote another
guy}{\mktsEnStyleMarkMain{}9} who
thinks so:

\begin{mktsEnvBlockquote}pull streams’ \# 1 superpower is their simplicity (in the Rich Hickey sense
of the word): anyone can write a full pull stream implementation from
scratch in a few minutes, from first principles. This is not true of node
streams in the slightest. Simplicity brings transparency with it, meaning
debugging and reasoning about implementation gets easier.

\end{mktsEnvBlockquote}

So while ‘simple’ doesn’t equal ‘easy’ (in the {\mktsStyleLinklabel{}Rich
Hickey}{\mktsEnStyleMarkMain{}10} sense of the word) it’s
still true that simpler concepts, a simpler implementation and a simpler API
are to be preferred over a convoluted implementation (and API) that suffers
from backward-compatibility pressures and maintains several parallel, mutually
exclusive and ultimately superfluous modes of operation. In the case of NodeJS
streams, you have ‘new style’ vs ‘old style’ mode of operation: A switch that
is done transparently based on what seemingly unrelated parts of the API you
employ in what order. Next, you must decide whether you’re dealing with
‘objects’ or ‘binary’ data, a completely gratuitous difference: it’s just
data. Lastly, you can structure your streaming app to do things the
{\mktsStyleCode{}.pipe()}ing way, or, alternatively, the {\mktsStyleCode{}EventEmitter} way—inheriting {\mktsStyleLinklabel{}all
that is wrong with the {\mktsStyleCode{}EventEmitter} API and
implementation}{\mktsEnStyleMarkMain{}11}.
To top it off, {\mktsStyleLinklabel{}you still don’t get proper error handling with NodeJS
streams}{\mktsEnStyleMarkMain{}12}.

\begin{mktsEnvBlockquote}I’m not saying that using the event handling model to process streams is
wrong, I just claim that having NodeJS do both piping and events where
either would have sufficed is what contributes to the rather sad performance
story they deliver.

\end{mktsEnvBlockquote}


{\mktsStyleBold{}I’m really sorry that these points amount to what can be perceived as bashing
on the NodeJS folks who have given us the great piece of software that is
NodeJS}. But frankly, as much as I like NodeJS, I nowadays try to stay away
from using the standard library’s streams and event emitters. Let’s just say not
everything in the Nodejs stdlib that {\mktsStyleItalic{}could\/} conceivably be used in userland
software built on that foundation {\mktsStyleItalic{}should\/} be used.

\mktsIndent{}With that off the chest, let’s move on to what PipeStreams claims to provide.

\mktsIndent{}[WIP]

\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}The }{\mktsStyleCode{}remit}{\mktsStyleBold{} methods, }{\mktsStyleCode{}\$()}{\mktsStyleBold{} and }{\mktsStyleCode{}\$async()}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Convenience stream transforms}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Circular pipelines}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Push sources}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Bridge to NodeJS streams}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}An (optional) convention for data events}


\makebox[\mktsLineheight][l]{{\mktstfPushRaise{-0.2}{-0.1}\mktsFontfileHanamina{}◼}}{\mktsStyleBold{}Tees: diverting into multiple sinks}

[1;32m0001 █ [0m  .p
[1;32m0010 █ [0m  ~start-paragraph
[1;32m0010 █ [0m  .comment '<'
[1;32m0010 █ [0m  .p
[1;32m0012 █ [0m  ~start-paragraph
[1;32m0012 █ [0m  .comment '<'
[1;32m0012 █ [0m  .p
[1;32m     █  [0m)document
[1;32m     █  [0m~stop
[1;32m     █  [0m# EOF