arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/querystream.js
node_modules/mongoose/lib/querystream.jsRaw
  1
  2/*!
  3 * Module dependencies.
  4 */
  5
  6var Stream = require('stream').Stream
  7var utils = require('./utils')
  8var helpers = require('./queryhelpers')
  9var K = function(k){ return k }
 10
 11/**
 12 * Provides a Node.js 0.8 style [ReadStream](http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream) interface for Queries.
 13 *
 14 *     var stream = Model.find().stream();
 15 *
 16 *     stream.on('data', function (doc) {
 17 *       // do something with the mongoose document
 18 *     }).on('error', function (err) {
 19 *       // handle the error
 20 *     }).on('close', function () {
 21 *       // the stream is closed
 22 *     });
 23 *
 24 *
 25 * The stream interface allows us to simply "plug-in" to other _Node.js 0.8_ style write streams.
 26 *
 27 *     Model.where('created').gte(twoWeeksAgo).stream().pipe(writeStream);
 28 *
 29 * ####Valid options
 30 *
 31 *   - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data`.
 32 *
 33 * ####Example
 34 *
 35 *     // JSON.stringify all documents before emitting
 36 *     var stream = Thing.find().stream({ transform: JSON.stringify });
 37 *     stream.pipe(writeStream);
 38 *
 39 * _NOTE: plugging into an HTTP response will *not* work out of the box. Those streams expect only strings or buffers to be emitted, so first formatting our documents as strings/buffers is necessary._
 40 *
 41 * _NOTE: these streams are Node.js 0.8 style read streams which differ from Node.js 0.10 style. Node.js 0.10 streams are not well tested yet and are not guaranteed to work._
 42 *
 43 * @param {Query} query
 44 * @param {Object} [options]
 45 * @inherits NodeJS Stream http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream
 46 * @event `data`: emits a single Mongoose document
 47 * @event `error`: emits when an error occurs during streaming. This will emit _before_ the `close` event.
 48 * @event `close`: emits when the stream reaches the end of the cursor or an error occurs, or the stream is manually `destroy`ed. After this event, no more events are emitted.
 49 * @api public
 50 */
 51
 52function QueryStream (query, options) {
 53  Stream.call(this);
 54
 55  this.query = query;
 56  this.readable = true;
 57  this.paused = false;
 58  this._cursor = null;
 59  this._destroyed = null;
 60  this._fields = null;
 61  this._buffer = null;
 62  this._inline = T_INIT;
 63  this._running = false;
 64  this._transform = options && 'function' == typeof options.transform
 65    ? options.transform
 66    : K;
 67
 68  // give time to hook up events
 69  var self = this;
 70  process.nextTick(function () {
 71    self._init();
 72  });
 73}
 74
 75/*!
 76 * Inherit from Stream
 77 */
 78
 79QueryStream.prototype.__proto__ = Stream.prototype;
 80
 81/**
 82 * Flag stating whether or not this stream is readable.
 83 *
 84 * @property readable
 85 * @api public
 86 */
 87
 88QueryStream.prototype.readable;
 89
 90/**
 91 * Flag stating whether or not this stream is paused.
 92 *
 93 * @property paused
 94 * @api public
 95 */
 96
 97QueryStream.prototype.paused;
 98
 99// trampoline flags
100var T_INIT = 0;
101var T_IDLE = 1;
102var T_CONT = 2;
103
104/**
105 * Initializes the query.
106 *
107 * @api private
108 */
109
110QueryStream.prototype._init = function () {
111  if (this._destroyed) return;
112
113  var query = this.query
114    , model = query.model
115    , options = query._optionsForExec(model)
116    , self = this
117
118  try {
119    query.cast(model);
120  } catch (err) {
121    return self.destroy(err);
122  }
123
124  self._fields = utils.clone(query._fields);
125  options.fields = query._castFields(self._fields);
126
127  model.collection.find(query._conditions, options, function (err, cursor) {
128    if (err) return self.destroy(err);
129    self._cursor = cursor;
130    self._next();
131  });
132}
133
134/**
135 * Trampoline for pulling the next doc from cursor.
136 *
137 * @see QueryStream#__next #querystream_QueryStream-__next
138 * @api private
139 */
140
141QueryStream.prototype._next = function _next () {
142  if (this.paused || this._destroyed) {
143    return this._running = false;
144  }
145
146  this._running = true;
147
148  if (this._buffer && this._buffer.length) {
149    var arg;
150    while (!this.paused && !this._destroyed && (arg = this._buffer.shift())) {
151      this._onNextObject.apply(this, arg);
152    }
153  }
154
155  // avoid stack overflows with large result sets.
156  // trampoline instead of recursion.
157  while (this.__next()) {}
158}
159
160/**
161 * Pulls the next doc from the cursor.
162 *
163 * @see QueryStream#_next #querystream_QueryStream-_next
164 * @api private
165 */
166
167QueryStream.prototype.__next = function () {
168  if (this.paused || this._destroyed)
169    return this._running = false;
170
171  var self = this;
172  self._inline = T_INIT;
173
174  self._cursor.nextObject(function cursorcb (err, doc) {
175    self._onNextObject(err, doc);
176  });
177
178  // if onNextObject() was already called in this tick
179  // return ourselves to the trampoline.
180  if (T_CONT === this._inline) {
181    return true;
182  } else {
183    // onNextObject() hasn't fired yet. tell onNextObject
184    // that its ok to call _next b/c we are not within
185    // the trampoline anymore.
186    this._inline = T_IDLE;
187  }
188}
189
190/**
191 * Transforms raw `doc`s returned from the cursor into a model instance.
192 *
193 * @param {Error|null} err
194 * @param {Object} doc
195 * @api private
196 */
197
198QueryStream.prototype._onNextObject = function _onNextObject (err, doc) {
199  if (this._destroyed) return;
200
201  if (this.paused) {
202    this._buffer || (this._buffer = []);
203    this._buffer.push([err, doc]);
204    return this._running = false;
205  }
206
207  if (err) return this.destroy(err);
208
209  // when doc is null we hit the end of the cursor
210  if (!doc) {
211    this.emit('end');
212    return this.destroy();
213  }
214
215  var opts = this.query.options;
216
217  if (!opts.populate) {
218    return true === opts.lean
219      ? emit(this, doc)
220      : createAndEmit(this, doc);
221  }
222
223  var self = this;
224  var pop = helpers.preparePopulationOptions(self.query, self.query.options);
225
226  self.query.model.populate(doc, pop, function (err, doc) {
227    if (err) return self.destroy(err);
228    return true === opts.lean
229      ? emit(self, doc)
230      : createAndEmit(self, doc);
231  })
232}
233
234function createAndEmit (self, doc) {
235  var instance = new self.query.model(undefined, self._fields, true);
236  instance.init(doc, function (err) {
237    if (err) return self.destroy(err);
238    emit(self, instance);
239  });
240}
241
242/*!
243 * Emit a data event and manage the trampoline state
244 */
245
246function emit (self, doc) {
247  self.emit('data', self._transform(doc));
248
249  // trampoline management
250  if (T_IDLE === self._inline) {
251    // no longer in trampoline. restart it.
252    self._next();
253  } else {
254    // in a trampoline. tell __next that its
255    // ok to continue jumping.
256    self._inline = T_CONT;
257  }
258}
259
260/**
261 * Pauses this stream.
262 *
263 * @api public
264 */
265
266QueryStream.prototype.pause = function () {
267  this.paused = true;
268}
269
270/**
271 * Resumes this stream.
272 *
273 * @api public
274 */
275
276QueryStream.prototype.resume = function () {
277  this.paused = false;
278
279  if (!this._cursor) {
280    // cannot start if not initialized
281    return;
282  }
283
284  // are we within the trampoline?
285  if (T_INIT === this._inline) {
286    return;
287  }
288
289  if (!this._running) {
290    // outside QueryStream control, need manual restart
291    return this._next();
292  }
293}
294
295/**
296 * Destroys the stream, closing the underlying cursor. No more events will be emitted.
297 *
298 * @param {Error} [err]
299 * @api public
300 */
301
302QueryStream.prototype.destroy = function (err) {
303  if (this._destroyed) return;
304  this._destroyed = true;
305  this._running = false;
306  this.readable = false;
307
308  if (this._cursor) {
309    this._cursor.close();
310  }
311
312  if (err) {
313    this.emit('error', err);
314  }
315
316  this.emit('close');
317}
318
319/**
320 * Pipes this query stream into another stream. This method is inherited from NodeJS Streams.
321 *
322 * ####Example:
323 *
324 *     query.stream().pipe(writeStream [, options])
325 *
326 * @method pipe
327 * @memberOf QueryStream
328 * @see NodeJS http://nodejs.org/api/stream.html
329 * @api public
330 */
331
332/*!
333 * Module exports
334 */
335
336module.exports = exports = QueryStream;