File size: 3,249 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
'use strict';

var Transform = require('streamx').Transform;

// Based on help from @mafintosh via https://gist.github.com/mafintosh/92836a8d03df0ef41356e233e0f06382

function toThrough(readable) {
  var highWaterMark = readable._readableState.highWaterMark;

  // Streamx uses 16384 as the default highWaterMark for everything and then
  // divides it by 1024 for objects
  // However, node's objectMode streams the number of objects as highWaterMark, so we need to
  // multiply the objectMode highWaterMark by 1024 to make it streamx compatible
  if (readable._readableState.objectMode) {
    highWaterMark = readable._readableState.highWaterMark * 1024;
  }

  var destroyedByError = false;
  var readableClosed = false;
  var readableEnded = false;

  function flush(cb) {
    var self = this;

    // Afer all writes have drained, we change the `_read` implementation
    self._read = function (cb) {
      readable.resume();
      cb();
    };

    readable.on('data', onData);
    readable.once('error', onError);
    readable.once('end', onEnd);

    function cleanup() {
      readable.off('data', onData);
      readable.off('error', onError);
      readable.off('end', onEnd);
    }

    function onData(data) {
      var drained = self.push(data);
      // When the stream is not drained, we pause it because `_read` will be called later
      if (!drained) {
        readable.pause();
      }
    }

    function onError(err) {
      cleanup();
      cb(err);
    }

    function onEnd() {
      cleanup();
      cb();
    }
  }

  // Handle the case where a user destroyed the returned stream
  function predestroy() {
    // Only call destroy on the readable if this `predestroy` wasn't
    // caused via the readable having an `error` or `close` event
    if (destroyedByError) {
      return;
    }
    if (readableClosed) {
      return;
    }
    readable.destroy(new Error('Wrapper destroyed'));
  }

  var wrapper = new Transform({
    highWaterMark: highWaterMark,
    flush: flush,
    predestroy: predestroy,
  });

  // Forward errors from the underlying stream
  readable.once('error', onError);
  readable.once('end', onEnd);
  readable.once('close', onClose);

  function onError(err) {
    destroyedByError = true;
    wrapper.destroy(err);
  }

  function onEnd() {
    readableEnded = true;
  }

  function onClose() {
    readableClosed = true;
    // Only destroy the wrapper if the readable hasn't ended successfully
    if (!readableEnded) {
      wrapper.destroy();
    }
  }

  var shouldFlow = true;
  wrapper.once('pipe', onPipe);
  wrapper.on('piping', onPiping);
  wrapper.on('newListener', onListener);

  function onPiping() {
    maybeFlow();
    wrapper.off('piping', onPiping);
    wrapper.off('newListener', onListener);
  }

  function onListener(event) {
    // Once we've seen the data or readable event, check if we need to flow
    if (event === 'data' || event === 'readable') {
      onPiping();
    }
  }

  function onPipe() {
    // If the wrapper is piped, disable flow
    shouldFlow = false;
  }

  function maybeFlow() {
    // If we need to flow, end the stream which triggers flush
    if (shouldFlow) {
      wrapper.end();
    }
  }

  return wrapper;
}

module.exports = toThrough;