UNPKG

recoder-code

Version:

Complete AI-powered development platform with ML model training, plugin registry, real-time collaboration, monitoring, infrastructure automation, and enterprise deployment capabilities

201 lines (174 loc) 5.84 kB
var emitter = require('../emitter'); var ACTIONS = require('../message-actions').ACTIONS; var util = require('../util'); // Queries are live requests to the database for particular sets of fields. // // The server actively tells the client when there's new data that matches // a set of conditions. module.exports = Query; function Query(action, connection, id, collection, query, options, callback) { emitter.EventEmitter.call(this); // 'qf' or 'qs' this.action = action; this.connection = connection; this.id = id; this.collection = collection; // The query itself. For mongo, this should look something like {"data.x":5} this.query = query; // A list of resulting documents. These are actual documents, complete with // data and all the rest. It is possible to pass in an initial results set, // so that a query can be serialized and then re-established this.results = null; if (options && options.results) { this.results = options.results; delete options.results; } this.extra = undefined; // Options to pass through with the query this.options = options; this.callback = callback; this.ready = false; this.sent = false; } emitter.mixin(Query); Query.prototype.hasPending = function() { return !this.ready; }; // Helper for subscribe & fetch, since they share the same message format. // // This function actually issues the query. Query.prototype.send = function() { if (!this.connection.canSend) return; var message = { a: this.action, id: this.id, c: this.collection, q: this.query }; if (this.options) { message.o = this.options; } if (this.results) { // Collect the version of all the documents in the current result set so we // don't need to be sent their snapshots again. var results = []; for (var i = 0; i < this.results.length; i++) { var doc = this.results[i]; results.push([doc.id, doc.version]); } message.r = results; } this.connection.send(message); this.sent = true; }; // Destroy the query object. Any subsequent messages for the query will be // ignored by the connection. Query.prototype.destroy = function(callback) { if (this.connection.canSend && this.action === ACTIONS.querySubscribe) { this.connection.send({a: ACTIONS.queryUnsubscribe, id: this.id}); } this.connection._destroyQuery(this); // There is a callback for consistency, but we don't actually wait for the // server's unsubscribe message currently if (callback) util.nextTick(callback); }; Query.prototype._onConnectionStateChanged = function() { if (this.connection.canSend && !this.sent) { this.send(); } else { this.sent = false; } }; Query.prototype._handleFetch = function(err, data, extra) { // Once a fetch query gets its data, it is destroyed. this.connection._destroyQuery(this); this._handleResponse(err, data, extra); }; Query.prototype._handleSubscribe = function(err, data, extra) { this._handleResponse(err, data, extra); }; Query.prototype._handleResponse = function(err, data, extra) { var callback = this.callback; this.callback = null; if (err) return this._finishResponse(err, callback); if (!data) return this._finishResponse(null, callback); var query = this; var wait = 1; var finish = function(err) { if (err) return query._finishResponse(err, callback); if (--wait) return; query._finishResponse(null, callback); }; if (Array.isArray(data)) { wait += data.length; this.results = this._ingestSnapshots(data, finish); this.extra = extra; } else { for (var id in data) { wait++; var snapshot = data[id]; var doc = this.connection.get(snapshot.c || this.collection, id); doc.ingestSnapshot(snapshot, finish); } } finish(); }; Query.prototype._ingestSnapshots = function(snapshots, finish) { var results = []; for (var i = 0; i < snapshots.length; i++) { var snapshot = snapshots[i]; var doc = this.connection.get(snapshot.c || this.collection, snapshot.d); doc.ingestSnapshot(snapshot, finish); results.push(doc); } return results; }; Query.prototype._finishResponse = function(err, callback) { this.emit('ready'); this.ready = true; if (err) { this.connection._destroyQuery(this); if (callback) return callback(err); return this.emit('error', err); } if (callback) callback(null, this.results, this.extra); }; Query.prototype._handleError = function(err) { this.emit('error', err); }; Query.prototype._handleDiff = function(diff) { // We need to go through the list twice. First, we'll ingest all the new // documents. After that we'll emit events and actually update our list. // This avoids race conditions around setting documents to be subscribed & // unsubscribing documents in event callbacks. for (var i = 0; i < diff.length; i++) { var d = diff[i]; if (d.type === 'insert') d.values = this._ingestSnapshots(d.values); } for (var i = 0; i < diff.length; i++) { var d = diff[i]; switch (d.type) { case 'insert': var newDocs = d.values; Array.prototype.splice.apply(this.results, [d.index, 0].concat(newDocs)); this.emit('insert', newDocs, d.index); break; case 'remove': var howMany = d.howMany || 1; var removed = this.results.splice(d.index, howMany); this.emit('remove', removed, d.index); break; case 'move': var howMany = d.howMany || 1; var docs = this.results.splice(d.from, howMany); Array.prototype.splice.apply(this.results, [d.to, 0].concat(docs)); this.emit('move', docs, d.from, d.to); break; } } this.emit('changed', this.results); }; Query.prototype._handleExtra = function(extra) { this.extra = extra; this.emit('extra', extra); };