keuss 2.0.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.yaml +3 -0
- package/CHANGELOG.md +1 -1
- package/Pipeline/BaseLink.js +1 -1
- package/Pipeline/Queue.js +106 -90
- package/Queue.js +13 -2
- package/README.md +4 -4
- package/backends/bucket-mongo-safe.js +164 -144
- package/backends/intraorder.js +92 -82
- package/backends/mongo.js +137 -95
- package/backends/pl-mongo.js +18 -30
- package/backends/ps-mongo.js +121 -110
- package/backends/redis-oq.js +0 -2
- package/backends/stream-mongo.js +81 -63
- package/lib/mubsub/channel.js +313 -0
- package/lib/mubsub/connection.js +79 -0
- package/lib/mubsub/index.js +10 -0
- package/package.json +8 -10
- package/signal/mongo-capped.js +1 -1
- package/stats/mongo.js +91 -54
package/backends/stream-mongo.js
CHANGED
|
@@ -7,10 +7,10 @@ const mongo = require ('mongodb');
|
|
|
7
7
|
const Queue = require ('../Queue');
|
|
8
8
|
const QFactory_MongoDB_defaults = require ('../QFactory-MongoDB-defaults');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const debug = require('debug')('keuss:Queue:StreamMongo');
|
|
11
11
|
|
|
12
|
-
class StreamMongoQueue extends Queue {
|
|
13
12
|
|
|
13
|
+
class StreamMongoQueue extends Queue {
|
|
14
14
|
//////////////////////////////////////////////
|
|
15
15
|
constructor (name, factory, opts, orig_opts) {
|
|
16
16
|
super (name, factory, opts, orig_opts);
|
|
@@ -48,7 +48,7 @@ class StreamMongoQueue extends Queue {
|
|
|
48
48
|
|
|
49
49
|
/////////////////////////////////////////
|
|
50
50
|
// add element to queue
|
|
51
|
-
insert (entry,
|
|
51
|
+
insert (entry, cb) {
|
|
52
52
|
const mtr = entry.mature;
|
|
53
53
|
const tr = entry.tries;
|
|
54
54
|
|
|
@@ -58,17 +58,18 @@ class StreamMongoQueue extends Queue {
|
|
|
58
58
|
|
|
59
59
|
entry.t = new Date();
|
|
60
60
|
|
|
61
|
-
this._col.insertOne (entry, {}
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
this._col.insertOne (entry, {})
|
|
62
|
+
.then (res => {
|
|
63
|
+
cb (null, res.insertedId);
|
|
64
64
|
this._groups_vector.forEach (i => this._stats.incr (`stream.${i}.put`));
|
|
65
|
-
})
|
|
65
|
+
})
|
|
66
|
+
.catch (cb);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
/////////////////////////////////////////
|
|
70
71
|
// get element from queue
|
|
71
|
-
get (
|
|
72
|
+
get (cb) {
|
|
72
73
|
const gid = this._gid;
|
|
73
74
|
const q = {};
|
|
74
75
|
|
|
@@ -82,30 +83,33 @@ class StreamMongoQueue extends Queue {
|
|
|
82
83
|
updt.$set[`mature.${gid}`] = Queue.nowPlusSecs (100 * this._opts.ttl);
|
|
83
84
|
|
|
84
85
|
const opts = {
|
|
85
|
-
sort: {}
|
|
86
|
+
sort: {},
|
|
87
|
+
includeResultMetadata: true
|
|
86
88
|
};
|
|
89
|
+
|
|
87
90
|
opts.sort[`mature.${gid}`] = 1;
|
|
88
91
|
|
|
89
92
|
debug ('get() with q %O, upd %O, opts %o', q, updt, opts);
|
|
90
93
|
|
|
91
|
-
this._col.findOneAndUpdate (q, updt, opts
|
|
92
|
-
|
|
93
|
-
const v =
|
|
94
|
-
if (!v) return
|
|
94
|
+
this._col.findOneAndUpdate (q, updt, opts)
|
|
95
|
+
.then (res => {
|
|
96
|
+
const v = res && res.value;
|
|
97
|
+
if (!v) return cb ();
|
|
95
98
|
if (v.payload._bsontype == 'Binary') v.payload = v.payload.buffer;
|
|
96
99
|
v.mature = v.mature[gid];
|
|
97
100
|
v.tries = v.tries[gid];
|
|
98
101
|
delete v.processed;
|
|
99
102
|
delete v.t;
|
|
100
|
-
|
|
103
|
+
cb (null, v);
|
|
101
104
|
this._stats.incr (`stream.${gid}.get`);
|
|
102
|
-
})
|
|
105
|
+
})
|
|
106
|
+
.catch (cb);
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
|
|
106
110
|
//////////////////////////////////
|
|
107
111
|
// reserve element: call cb (err, pl) where pl has an id
|
|
108
|
-
reserve (
|
|
112
|
+
reserve (cb) {
|
|
109
113
|
const gid = this._gid;
|
|
110
114
|
const delay = this._opts.reserve_delay || 120;
|
|
111
115
|
|
|
@@ -124,41 +128,44 @@ class StreamMongoQueue extends Queue {
|
|
|
124
128
|
|
|
125
129
|
const opts = {
|
|
126
130
|
sort: {},
|
|
127
|
-
returnDocument: 'before'
|
|
131
|
+
returnDocument: 'before',
|
|
132
|
+
includeResultMetadata: true
|
|
128
133
|
};
|
|
134
|
+
|
|
129
135
|
opts.sort[`mature.${gid}`] = 1;
|
|
130
136
|
|
|
131
137
|
debug ('reserve() with q %O, upd %O, opts %o', q, updt, opts);
|
|
132
138
|
|
|
133
|
-
this._col.findOneAndUpdate (q, updt, opts
|
|
134
|
-
|
|
135
|
-
const v =
|
|
136
|
-
if (!v) return
|
|
139
|
+
this._col.findOneAndUpdate (q, updt, opts)
|
|
140
|
+
.then (res => {
|
|
141
|
+
const v = res && res.value;
|
|
142
|
+
if (!v) return cb ();
|
|
137
143
|
if (v.payload._bsontype == 'Binary') v.payload = v.payload.buffer;
|
|
138
144
|
v.mature = v.mature[gid];
|
|
139
145
|
v.tries = v.tries[gid];
|
|
140
146
|
delete v.processed;
|
|
141
147
|
delete v.t;
|
|
142
|
-
|
|
148
|
+
cb (null, v);
|
|
143
149
|
this._stats.incr (`stream.${gid}.reserve`);
|
|
144
|
-
})
|
|
150
|
+
})
|
|
151
|
+
.catch (cb);
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
|
|
148
155
|
//////////////////////////////////
|
|
149
156
|
// commit previous reserve, by p.id
|
|
150
|
-
commit (id,
|
|
157
|
+
commit (id, cb) {
|
|
151
158
|
const gid = this._gid;
|
|
152
159
|
let q;
|
|
153
160
|
|
|
154
161
|
try {
|
|
155
162
|
q = {
|
|
156
|
-
_id: (_.isString(id) ? new mongo.
|
|
163
|
+
_id: (_.isString(id) ? new mongo.ObjectId (id) : id),
|
|
157
164
|
};
|
|
158
165
|
q[`reserved.${gid}`] = {$exists: true};
|
|
159
166
|
}
|
|
160
167
|
catch (e) {
|
|
161
|
-
return
|
|
168
|
+
return cb ('id [' + id + '] can not be used as rollback id: ' + e);
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
const updt = {
|
|
@@ -173,33 +180,34 @@ class StreamMongoQueue extends Queue {
|
|
|
173
180
|
|
|
174
181
|
debug ('commit() with q %O, upd %O, opts %o', q, updt, opts);
|
|
175
182
|
|
|
176
|
-
this._col.updateOne (q, updt, opts
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
this._col.updateOne (q, updt, opts)
|
|
184
|
+
.then (res => {
|
|
185
|
+
cb (null, res && (res.modifiedCount == 1));
|
|
179
186
|
this._stats.incr (`stream.${gid}.commit`);
|
|
180
|
-
})
|
|
187
|
+
})
|
|
188
|
+
.catch (cb);
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
|
|
184
192
|
//////////////////////////////////
|
|
185
193
|
// rollback previous reserve, by p.id
|
|
186
|
-
rollback (id, next_t,
|
|
194
|
+
rollback (id, next_t, cb) {
|
|
187
195
|
const gid = this._gid;
|
|
188
196
|
let q;
|
|
189
197
|
|
|
190
198
|
if (_.isFunction (next_t)) {
|
|
191
|
-
|
|
199
|
+
cb = next_t;
|
|
192
200
|
next_t = null;
|
|
193
201
|
}
|
|
194
202
|
|
|
195
203
|
try {
|
|
196
204
|
q = {
|
|
197
|
-
_id: (_.isString(id) ? new mongo.
|
|
205
|
+
_id: (_.isString(id) ? new mongo.ObjectId (id) : id),
|
|
198
206
|
};
|
|
199
207
|
q[`reserved.${gid}`] = {$exists: true};
|
|
200
208
|
}
|
|
201
209
|
catch (e) {
|
|
202
|
-
return
|
|
210
|
+
return cb ('id [' + id + '] can not be used as rollback id: ' + e);
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
const updt = {
|
|
@@ -213,30 +221,33 @@ class StreamMongoQueue extends Queue {
|
|
|
213
221
|
|
|
214
222
|
debug ('rollback() with q %O, upd %O, opts %o', q, updt, opts);
|
|
215
223
|
|
|
216
|
-
this._col.updateOne (q, updt, opts
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
this._col.updateOne (q, updt, opts)
|
|
225
|
+
.then (res => {
|
|
226
|
+
cb (null, res && (res.modifiedCount == 1));
|
|
219
227
|
this._stats.incr (`stream.${gid}.rollback`);
|
|
220
|
-
})
|
|
228
|
+
})
|
|
229
|
+
.catch (cb);
|
|
221
230
|
}
|
|
222
231
|
|
|
223
232
|
|
|
224
233
|
//////////////////////////////////
|
|
225
234
|
// queue size including non-mature elements
|
|
226
|
-
totalSize (
|
|
235
|
+
totalSize (cb, gid) {
|
|
227
236
|
const gr = gid || this._gid;
|
|
228
237
|
|
|
229
238
|
const q = {};
|
|
230
239
|
q[`processed.${gr}`] = false;
|
|
231
240
|
|
|
232
241
|
const opts = {};
|
|
233
|
-
this._col.countDocuments (q, opts
|
|
242
|
+
this._col.countDocuments (q, opts)
|
|
243
|
+
.then (res => cb (null, res))
|
|
244
|
+
.catch (cb);
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
|
|
237
248
|
//////////////////////////////////
|
|
238
249
|
// queue size NOT including non-mature elements
|
|
239
|
-
size (
|
|
250
|
+
size (cb, gid) {
|
|
240
251
|
const gr = gid || this._gid;
|
|
241
252
|
|
|
242
253
|
const q = {};
|
|
@@ -244,13 +255,15 @@ class StreamMongoQueue extends Queue {
|
|
|
244
255
|
q[`mature.${gr}`] = {$lte: Queue.now()};
|
|
245
256
|
|
|
246
257
|
const opts = {};
|
|
247
|
-
this._col.countDocuments (q, opts
|
|
258
|
+
this._col.countDocuments (q, opts)
|
|
259
|
+
.then (res => cb (null, res))
|
|
260
|
+
.catch (cb);
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
|
|
251
264
|
//////////////////////////////////
|
|
252
265
|
// queue size of non-mature elements only
|
|
253
|
-
schedSize (
|
|
266
|
+
schedSize (cb, gid) {
|
|
254
267
|
const gr = gid || this._gid;
|
|
255
268
|
|
|
256
269
|
const q = {};
|
|
@@ -259,13 +272,15 @@ class StreamMongoQueue extends Queue {
|
|
|
259
272
|
q[`mature.${gr}`] = {$gt: Queue.now()};
|
|
260
273
|
|
|
261
274
|
const opts = {};
|
|
262
|
-
this._col.countDocuments (q, opts
|
|
275
|
+
this._col.countDocuments (q, opts)
|
|
276
|
+
.then (res => cb (null, res))
|
|
277
|
+
.catch (cb);
|
|
263
278
|
}
|
|
264
279
|
|
|
265
280
|
|
|
266
281
|
//////////////////////////////////
|
|
267
282
|
// queue size of reserved elements only
|
|
268
|
-
resvSize (
|
|
283
|
+
resvSize (cb, gid) {
|
|
269
284
|
const gr = gid || this._gid;
|
|
270
285
|
|
|
271
286
|
const q = {};
|
|
@@ -274,13 +289,15 @@ class StreamMongoQueue extends Queue {
|
|
|
274
289
|
q[`mature.${gr}`] = {$gt: Queue.now()};
|
|
275
290
|
|
|
276
291
|
const opts = {};
|
|
277
|
-
this._col.countDocuments (q, opts
|
|
292
|
+
this._col.countDocuments (q, opts)
|
|
293
|
+
.then (res => cb (null, res))
|
|
294
|
+
.catch (cb);
|
|
278
295
|
}
|
|
279
296
|
|
|
280
297
|
|
|
281
298
|
/////////////////////////////////////////
|
|
282
299
|
// get element from queue
|
|
283
|
-
next_t (
|
|
300
|
+
next_t (cb, gid) {
|
|
284
301
|
const gr = gid || this._gid;
|
|
285
302
|
|
|
286
303
|
const q = {};
|
|
@@ -294,11 +311,12 @@ class StreamMongoQueue extends Queue {
|
|
|
294
311
|
.limit(1)
|
|
295
312
|
.sort (sort)
|
|
296
313
|
.project ({mature:1})
|
|
297
|
-
.next (
|
|
298
|
-
|
|
299
|
-
debug ('next_t with git %s: got %o', gr,
|
|
300
|
-
|
|
301
|
-
})
|
|
314
|
+
.next ()
|
|
315
|
+
.then (res => {
|
|
316
|
+
debug ('next_t with git %s: got %o', gr, res);
|
|
317
|
+
cb (null, res && res.mature && res.mature[gr]);
|
|
318
|
+
})
|
|
319
|
+
.catch (cb);
|
|
302
320
|
}
|
|
303
321
|
|
|
304
322
|
|
|
@@ -398,10 +416,14 @@ class StreamMongoQueue extends Queue {
|
|
|
398
416
|
this._groups_vector.forEach (i => {
|
|
399
417
|
const idx = {};
|
|
400
418
|
idx[`mature.${i}`] = 1;
|
|
401
|
-
tasks.push (cb => this._col.createIndex (idx, cb));
|
|
419
|
+
tasks.push (cb => this._col.createIndex (idx).then (res => cb(null, res)).catch (cb));
|
|
402
420
|
});
|
|
403
|
-
|
|
404
|
-
|
|
421
|
+
|
|
422
|
+
tasks.push (cb => this._col.createIndex ({t: 1}, {expireAfterSeconds: this._opts.ttl}).then (res => cb(null, res)).catch (cb));
|
|
423
|
+
|
|
424
|
+
async.series (tasks)
|
|
425
|
+
.then (() => cb (null, this))
|
|
426
|
+
.catch (cb);
|
|
405
427
|
}
|
|
406
428
|
}
|
|
407
429
|
|
|
@@ -456,16 +478,12 @@ function creator (opts, cb) {
|
|
|
456
478
|
const _opts = opts || {};
|
|
457
479
|
const m_url = _opts.url || 'mongodb://localhost:27017/keuss';
|
|
458
480
|
|
|
459
|
-
MongoClient.connect (m_url
|
|
460
|
-
|
|
481
|
+
MongoClient.connect (m_url)
|
|
482
|
+
.then (cl => {
|
|
461
483
|
const F = new Factory (_opts, cl);
|
|
462
484
|
F.async_init (err => cb (null, F));
|
|
463
|
-
})
|
|
485
|
+
})
|
|
486
|
+
.catch (cb);
|
|
464
487
|
}
|
|
465
488
|
|
|
466
489
|
module.exports = creator;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
const EventEmitter = require('events')
|
|
2
|
+
const noop = function () {}
|
|
3
|
+
|
|
4
|
+
class Channel extends EventEmitter {
|
|
5
|
+
/**
|
|
6
|
+
* Channel constructor.
|
|
7
|
+
*
|
|
8
|
+
* @param {Connection} connection
|
|
9
|
+
* @param {String} [name] optional channel/collection name, default is 'mubsub'
|
|
10
|
+
* @param {Object} [options] optional options
|
|
11
|
+
* - `size` max size of the collection in bytes, default is 5mb
|
|
12
|
+
* - `max` max amount of documents in the collection
|
|
13
|
+
* - `retryInterval` time in ms to wait if no docs found, default is 200ms
|
|
14
|
+
* - `recreate` recreate the tailable cursor on error, default is true
|
|
15
|
+
* @api public
|
|
16
|
+
*/
|
|
17
|
+
constructor (connection, name, options) {
|
|
18
|
+
super()
|
|
19
|
+
options || (options = {})
|
|
20
|
+
this.options = {
|
|
21
|
+
recreate: typeof options.recreate === 'boolean' ? options.recreate : true,
|
|
22
|
+
retryInterval:
|
|
23
|
+
typeof options.retryInterval === 'number' ? options.retryInterval : 200
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.collectionOpts = {
|
|
27
|
+
capped: true,
|
|
28
|
+
size: options.size || 1024 * 1024 * 5,
|
|
29
|
+
max: options.max
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.connection = connection
|
|
33
|
+
this.closed = false
|
|
34
|
+
this.listening = null
|
|
35
|
+
this.name = name || 'mubsub'
|
|
36
|
+
|
|
37
|
+
this.create().listen()
|
|
38
|
+
this.setMaxListeners(0)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Close the channel.
|
|
43
|
+
*
|
|
44
|
+
* @return {Channel} this
|
|
45
|
+
* @api public
|
|
46
|
+
*/
|
|
47
|
+
close () {
|
|
48
|
+
this.closed = true
|
|
49
|
+
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Publish an event.
|
|
55
|
+
*
|
|
56
|
+
* @param {String} event
|
|
57
|
+
* @param {Object} [message]
|
|
58
|
+
* @param {Function} [callback]
|
|
59
|
+
* @return {Channel} this
|
|
60
|
+
* @api public
|
|
61
|
+
*/
|
|
62
|
+
publish (event, message, callback) {
|
|
63
|
+
callback || (callback = noop)
|
|
64
|
+
|
|
65
|
+
this.ready(function (collection) {
|
|
66
|
+
collection
|
|
67
|
+
.insertOne({ event, message })
|
|
68
|
+
.then((result) => {
|
|
69
|
+
callback(null, { _id: result.insertedId })
|
|
70
|
+
})
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
callback(err)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Subscribe an event.
|
|
81
|
+
*
|
|
82
|
+
* @param {String} [event] if no event passed - all events are subscribed.
|
|
83
|
+
* @param {Function} callback
|
|
84
|
+
* @return {Object} unsubscribe function
|
|
85
|
+
* @api public
|
|
86
|
+
*/
|
|
87
|
+
subscribe (event, callback) {
|
|
88
|
+
const self = this
|
|
89
|
+
|
|
90
|
+
if (typeof event === 'function') {
|
|
91
|
+
callback = event
|
|
92
|
+
event = 'message'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.on(event, callback)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
unsubscribe: function () {
|
|
99
|
+
self.removeListener(event, callback)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create a channel collection.
|
|
106
|
+
*
|
|
107
|
+
* @return {Channel} this
|
|
108
|
+
* @api private
|
|
109
|
+
*/
|
|
110
|
+
create () {
|
|
111
|
+
const self = this
|
|
112
|
+
|
|
113
|
+
function create () {
|
|
114
|
+
self.connection.db
|
|
115
|
+
.createCollection(self.name, self.collectionOpts)
|
|
116
|
+
.then((collection) => {
|
|
117
|
+
self.collection = collection
|
|
118
|
+
self.emit('collection', self.collection)
|
|
119
|
+
})
|
|
120
|
+
.catch((err) => {
|
|
121
|
+
const collectionExistsError =
|
|
122
|
+
err &&
|
|
123
|
+
(err.codeName === 'NamespaceExists' ||
|
|
124
|
+
err.message === 'collection already exists' ||
|
|
125
|
+
err.message.toLowerCase().includes('collection already exists') ||
|
|
126
|
+
err.message.match(/^a collection.+already exists$/) ||
|
|
127
|
+
err.message.match(/^Collection.+already exists\.$/))
|
|
128
|
+
if (collectionExistsError) {
|
|
129
|
+
const collection = self.connection.db.collection(self.name)
|
|
130
|
+
collection.isCapped().then((capped) => {
|
|
131
|
+
if (!capped) {
|
|
132
|
+
self.emit(
|
|
133
|
+
'error',
|
|
134
|
+
new Error(`${err.message}, but it's NOT capped.`)
|
|
135
|
+
)
|
|
136
|
+
} else {
|
|
137
|
+
self.collection = collection
|
|
138
|
+
self.emit('collection', self.collection)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
} else if (err) {
|
|
142
|
+
self.emit('error', err)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.connection.db ? create() : this.connection.once('connect', create)
|
|
148
|
+
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create a listener which will emit events for subscribers.
|
|
154
|
+
* It will listen to any document with event property.
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} [latest] latest document to start listening from
|
|
157
|
+
* @return {Channel} this
|
|
158
|
+
* @api private
|
|
159
|
+
*/
|
|
160
|
+
listen (latest) {
|
|
161
|
+
const self = this
|
|
162
|
+
|
|
163
|
+
this.latest(
|
|
164
|
+
latest,
|
|
165
|
+
this.handle(true, function (latest, collection) {
|
|
166
|
+
const cursor = collection.find(
|
|
167
|
+
{ _id: { $gt: latest._id } },
|
|
168
|
+
{
|
|
169
|
+
tailable: true,
|
|
170
|
+
awaitData: true,
|
|
171
|
+
timeout: false,
|
|
172
|
+
maxAwaitTimeMS: self.options.retryInterval
|
|
173
|
+
}
|
|
174
|
+
).hint({ $natural: 1 })
|
|
175
|
+
const next = self.handle(function (doc) {
|
|
176
|
+
// There is no document only if the cursor is closed by accident.
|
|
177
|
+
// F.e. if collection was dropped or connection died.
|
|
178
|
+
if (!doc) {
|
|
179
|
+
return setTimeout(function () {
|
|
180
|
+
self.emit('error', new Error('Mubsub: broken cursor.'))
|
|
181
|
+
if (self.options.recreate) {
|
|
182
|
+
self.create().listen(latest)
|
|
183
|
+
}
|
|
184
|
+
}, 1000)
|
|
185
|
+
}
|
|
186
|
+
latest = doc
|
|
187
|
+
if (doc.event) {
|
|
188
|
+
self.emit(doc.event, doc.message)
|
|
189
|
+
self.emit('message', doc.message)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
self.emit('document', doc)
|
|
193
|
+
process.nextTick(more)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const more = function () {
|
|
197
|
+
cursor
|
|
198
|
+
.next()
|
|
199
|
+
.then((doc) => next(undefined, doc))
|
|
200
|
+
.catch((err) => next(err))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
more()
|
|
204
|
+
self.listening = collection
|
|
205
|
+
self.emit('ready', collection)
|
|
206
|
+
})
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return this
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the latest document from the collection. Insert a dummy object in case
|
|
214
|
+
* the collection is empty, because otherwise we don't get a tailable cursor
|
|
215
|
+
* and need to poll in a loop.
|
|
216
|
+
*
|
|
217
|
+
* @param {Object} [latest] latest known document
|
|
218
|
+
* @param {Function} callback
|
|
219
|
+
* @return {Channel} this
|
|
220
|
+
* @api private
|
|
221
|
+
*/
|
|
222
|
+
latest (latest, callback) {
|
|
223
|
+
function onCollection (collection) {
|
|
224
|
+
const cursor = collection
|
|
225
|
+
.find(latest ? { _id: latest._id } : {}, { timeout: false })
|
|
226
|
+
.hint({ $natural: -1 })
|
|
227
|
+
.limit(1)
|
|
228
|
+
cursor
|
|
229
|
+
.next()
|
|
230
|
+
.then((doc) => {
|
|
231
|
+
cursor.close() // Is this required?
|
|
232
|
+
if (doc) { // further check if there are larger _id than this doc.
|
|
233
|
+
collection.findOne({ _id: { $gt: doc._id } }, { sort: { _id: -1 } }).then((record) => {
|
|
234
|
+
if (record) {
|
|
235
|
+
return callback(undefined, record, collection)
|
|
236
|
+
}
|
|
237
|
+
return callback(undefined, doc, collection)
|
|
238
|
+
}).catch(err => {
|
|
239
|
+
console.warn(`failed to find the largest _id: ${err.message}, ignore it.`)
|
|
240
|
+
return callback(undefined, doc, collection)
|
|
241
|
+
})
|
|
242
|
+
} else {
|
|
243
|
+
collection
|
|
244
|
+
.insertOne({ dummy: true })
|
|
245
|
+
.then((result) => {
|
|
246
|
+
callback(null, { _id: result.insertedId }, collection)
|
|
247
|
+
})
|
|
248
|
+
.catch((err) => {
|
|
249
|
+
callback(err, undefined, collection)
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
.catch((err) => {
|
|
254
|
+
callback(err, undefined, collection)
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.collection
|
|
259
|
+
? onCollection(this.collection)
|
|
260
|
+
: this.once('collection', onCollection)
|
|
261
|
+
|
|
262
|
+
return this
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Return a function which will handle errors and consider channel and connection
|
|
267
|
+
* state.
|
|
268
|
+
*
|
|
269
|
+
* @param {Boolean} [exit] if error happens and exit is true, callback will not be called
|
|
270
|
+
* @param {Function} callback
|
|
271
|
+
* @return {Function}
|
|
272
|
+
* @api private
|
|
273
|
+
*/
|
|
274
|
+
handle (exit, callback) {
|
|
275
|
+
const self = this
|
|
276
|
+
|
|
277
|
+
if (typeof exit === 'function') {
|
|
278
|
+
callback = exit
|
|
279
|
+
exit = null
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return function () {
|
|
283
|
+
if (self.closed || self.connection.destroyed) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const args = [].slice.call(arguments)
|
|
288
|
+
const err = args.shift()
|
|
289
|
+
if (err) self.emit('error', err)
|
|
290
|
+
if (err && exit) return
|
|
291
|
+
callback.apply(self, args)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Call back if collection is ready for publishing.
|
|
297
|
+
*
|
|
298
|
+
* @param {Function} callback
|
|
299
|
+
* @return {Channel} this
|
|
300
|
+
* @api private
|
|
301
|
+
*/
|
|
302
|
+
ready (callback) {
|
|
303
|
+
if (this.listening) {
|
|
304
|
+
callback(this.listening)
|
|
305
|
+
} else {
|
|
306
|
+
this.once('ready', callback)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return this
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = Channel
|