Mirror: 馃帺 A tiny but capable push & pull stream library for TypeScript and Flow
1--- 2title: Architecture 3order: 1 4--- 5 6It may be useful to understand how Wonka's sources work internally 7if you want to write a new operator from scratch or contribute to it. 8 9This section explains how Wonka works internally and how it differs from 10the callbag specification. 11 12## Just Functions 13 14Internally Wonka only uses functions with rather simple signatures to 15make its streams work. 16 17We have sinks on one end, which need to receive values, and sources 18on the other, which need to send values. 19The sink is therefore just a function that we call with values over time. 20This is called a "push" signal. 21 22Because a sink has a start, incoming values, and an end, there are three 23signals that a sink can receive: `Start`, `Push`, and `End`. 24 25```typescript 26type Start = { tag: 0 }; // & [TalkbackFn] 27type Push<T> = { tag: 1 } & [T]; 28type End = 0; 29 30type Signal<T> = Start | Push<T> | End; 31 32type Sink<T> = (signal: Signal<T>) => void; 33``` 34 35As shown, the sink is just a function accepting a signal as its argument. 36 37When the stream starts then the sink is called with `Start`, 38Then for every incoming, new value it's called with `Push<T>`, 39and when the stream ends it's finally called with `End`. 40 41Since we want a source to send these values to the sink, the source is 42also just a function and it accepts a sink as its argument. 43 44```typescript 45type Source<T> = (sink: Sink<T>) => void; 46``` 47 48This is completely sufficient to represent simple "push" streams, where 49values are pushed from the source to the sink. They essentially flow from 50the "top" to the "bottom". 51 52Operators are just functions that transform a source. They take a 53source and some number of arguments and return a new source. 54Internally they may also create a new sink function that wraps the 55sink that their source will be called with. 56 57The type signature of an operator with no other arguments is thus: 58 59```typescript 60type Operator<In, Out> = (source: Source<In>) => Source<Out>; 61/* which is the same as: */ 62type Operator<In, Out> = (source: Source<In>) => (sink: Sink<Out>) => void; 63``` 64 65## Adding Callbacks 66 67To complete this pattern we're still missing a single piece: callbacks! 68 69Previously, we've looked at how sources are functions that accept sinks, which 70in turn are functions accepting a signal. What we're now missing is what makes 71Wonka's streams also work as iterables. 72 73We'd also like to be able to _cancel_ streams, so that we can interrupt 74them and not receive any more values. 75 76We can achieve this by passing a callback function on when a stream starts. 77In Wonka, a sink's `Start` signal also carries a callback that is used to communicate 78back to the source, making these "talkback signals" flow from the bottom to the top. 79 80```typescript 81const enum TalkbackKind { 82 Pull = 0, 83 Close = 1, 84} 85 86type TalkbackFn = (signal: TalkbackKind) => void; 87type Start = { tag: 0 } & [TalkbackFn]; 88``` 89 90This is like the previous `Signal<T>` definition, but the `Start` signal has the 91callback definition now. The callback accepts one of two signals: `Pull` or `Close`. 92 93`Close` is a signal that will cancel the stream. It tells the source to stop sending 94new values. 95 96The `Pull` signal is a signal that asks the source to send the next value. This is 97especially useful to represent iterables. In practice a user would never send this 98signal explicitly, but sinks would send the signal automatically after receiving the 99previous value from the stream. 100 101In asynchronous streams the `Pull` signal is of course a no-op. It won't do 102anything since we can't ask for asynchronous values. 103 104## Comparison to Callbags 105 106This is the full pattern of Wonka's streams and it's a little different from callbags. 107These changes have been made to make Wonka's streams typesafe. But there's 108also a small omission that makes Wonka's streams easier to explain. 109 110In Callbags, sources don't just accept sinks as their only argument. In fact, in 111callbags the source would also receive three different signals. This can be useful 112to represent "subjects". 113 114A subject is a sink and source combined. It can be used to dispatch values imperatively, 115like an event dispatcher. 116 117In Wonka there's a separate type for subjects however, since this reduces the 118complexity of its streams a lot: 119 120```reason 121interface Subject<T> { 122 next(value: T): void; 123 complete(): void; 124 source: Source<T>; 125} 126``` 127 128Hence in Wonka a subject is simply a wrapper around a source and a `next` and `complete` 129method.