keuss 2.0.7 → 2.2.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/Pipeline/BaseLink.js +1 -1
- package/Pipeline/Queue.js +114 -89
- package/Queue.js +15 -2
- package/backends/bucket-mongo-safe.js +166 -145
- package/backends/intraorder.js +94 -83
- package/backends/mongo.js +147 -95
- package/backends/pl-mongo.js +20 -31
- package/backends/postgres.js +16 -3
- package/backends/ps-mongo.js +131 -110
- package/backends/redis-list.js +2 -1
- package/backends/redis-oq.js +2 -3
- package/backends/stream-mongo.js +83 -64
- package/lib/mubsub/channel.js +313 -0
- package/lib/mubsub/connection.js +79 -0
- package/lib/mubsub/index.js +10 -0
- package/package.json +5 -7
- package/signal/mongo-capped.js +1 -1
- package/stats/mongo.js +91 -54
- package/utils/RedisOrderedQueue.js +23 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const EventEmitter = require('events')
|
|
2
|
+
const MongoClient = require('mongodb').MongoClient
|
|
3
|
+
const Channel = require('./channel')
|
|
4
|
+
const noop = function () {}
|
|
5
|
+
|
|
6
|
+
class Connection extends EventEmitter {
|
|
7
|
+
constructor (uri, options) {
|
|
8
|
+
super()
|
|
9
|
+
const self = this
|
|
10
|
+
|
|
11
|
+
options || (options = {})
|
|
12
|
+
|
|
13
|
+
// It's a Db instance.
|
|
14
|
+
if (uri.collection) {
|
|
15
|
+
this.db = uri
|
|
16
|
+
} else {
|
|
17
|
+
const p = MongoClient.connect(uri, options)
|
|
18
|
+
p.then((client) => {
|
|
19
|
+
self.client = client
|
|
20
|
+
self.db = client.db()
|
|
21
|
+
self.emit('connect', self.db)
|
|
22
|
+
self.client.on('error', function (err) {
|
|
23
|
+
self.emit('error', err)
|
|
24
|
+
})
|
|
25
|
+
}).catch((err) => {
|
|
26
|
+
self.emit('error', err)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.destroyed = false
|
|
31
|
+
this.channels = {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get state () {
|
|
35
|
+
let state
|
|
36
|
+
|
|
37
|
+
// Using 'destroyed' to be compatible with the driver.
|
|
38
|
+
if (this.destroyed) {
|
|
39
|
+
state = 'destroyed'
|
|
40
|
+
} else if (
|
|
41
|
+
(this.db && !this.client) ||
|
|
42
|
+
this.client?.topology?.isConnected()
|
|
43
|
+
) {
|
|
44
|
+
// https://github.com/mongodb/node-mongodb-native/blob/d266158c9e968c92e8041211ef99f1783025be40/src/operations/connect.ts#L18
|
|
45
|
+
state = 'connected'
|
|
46
|
+
} else {
|
|
47
|
+
state = 'connecting'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return state
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
channel (name, options) {
|
|
54
|
+
if (typeof name === 'object') {
|
|
55
|
+
options = name
|
|
56
|
+
name = 'mubsub'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.channels[name] || this.channels[name].closed) {
|
|
60
|
+
this.channels[name] = new Channel(this, name, options)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this.channels[name]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
close (callback) {
|
|
67
|
+
this.destroyed = true
|
|
68
|
+
if (this.client) {
|
|
69
|
+
callback || (callback = noop)
|
|
70
|
+
this.client
|
|
71
|
+
.close()
|
|
72
|
+
.then(() => callback())
|
|
73
|
+
.catch((e) => callback(e))
|
|
74
|
+
}
|
|
75
|
+
return this
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = Connection
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const Connection = require('./connection')
|
|
2
|
+
const Channel = require('./channel')
|
|
3
|
+
const mongodb = require('mongodb')
|
|
4
|
+
|
|
5
|
+
module.exports = exports = function (uri, options) {
|
|
6
|
+
return new Connection(uri, options)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
exports.Connection = Connection
|
|
10
|
+
exports.Channel = Channel
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keuss",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"queue",
|
|
6
6
|
"persistent",
|
|
@@ -28,23 +28,21 @@
|
|
|
28
28
|
},
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@nodebb/mubsub": "~1.8.0",
|
|
32
31
|
"async": "~3.2.6",
|
|
33
32
|
"async-lock": "~1.4.1",
|
|
34
33
|
"debug": "~4.3.6",
|
|
35
34
|
"ioredis": "~5.4.1",
|
|
36
35
|
"lodash": "~4.17.21",
|
|
37
36
|
"mitt": "~3.0.1",
|
|
38
|
-
"mongodb": "
|
|
37
|
+
"mongodb": "^6.0.0",
|
|
39
38
|
"uuid": "~8.3.2",
|
|
40
39
|
"pg": "~8.12.0"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
|
-
"chance": "~1.1.
|
|
44
|
-
"mocha": "~
|
|
42
|
+
"chance": "~1.1.13",
|
|
43
|
+
"mocha": "~11.7.5",
|
|
45
44
|
"should": "~13.2.3",
|
|
46
|
-
"nyc": "~17.
|
|
47
|
-
"why-is-node-running": "~3.2.0"
|
|
45
|
+
"nyc": "~17.1.0"
|
|
48
46
|
},
|
|
49
47
|
"scripts": {
|
|
50
48
|
"test": "docker compose up -d; sleep 5; mocha --reporter spec --check-leaks --no-timeouts --exit test/ ; docker compose down",
|
package/signal/mongo-capped.js
CHANGED
package/stats/mongo.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const _ = require ('lodash');
|
|
2
|
+
const async = require ('async');
|
|
3
|
+
const MongoClient = require ('mongodb').MongoClient;
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const Stats = require ('../Stats');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const debug = require('debug')('keuss:Stats:Mongo');
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
9
11
|
/*
|
|
10
12
|
* plain into a single mongo coll
|
|
11
13
|
*/
|
|
@@ -16,52 +18,62 @@ class MongoStats extends Stats {
|
|
|
16
18
|
this._opts = opts || {};
|
|
17
19
|
this._cache = {};
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
const upd = {
|
|
20
22
|
$set: {
|
|
21
23
|
ns: this._ns,
|
|
22
24
|
name: this._name,
|
|
23
25
|
}
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
this._coll().updateOne ({_id: this._id}, upd, {upsert: true}
|
|
28
|
+
this._coll().updateOne ({_id: this._id}, upd, {upsert: true})
|
|
29
|
+
.finally (() => {
|
|
27
30
|
debug ('mongo stats created, ns %s, name %s, opts %j', ns, name, opts);
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
values(cb) {
|
|
32
|
-
this._coll().findOne ({_id: this._id}, {projection: {counters: 1}}, (err, res) => {
|
|
33
|
-
if (err) return cb (err);
|
|
34
34
|
|
|
35
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
36
|
+
values(cb) {
|
|
37
|
+
this._coll().findOne ({_id: this._id}, {projection: {counters: 1}})
|
|
38
|
+
.then (res => {
|
|
35
39
|
debug ('mongo stats: get %s -> %j', this._name, res);
|
|
36
40
|
cb (null, (res && res.counters) || {});
|
|
37
|
-
})
|
|
41
|
+
})
|
|
42
|
+
.catch (cb);
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
|
|
46
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
40
47
|
paused (val, cb) {
|
|
41
48
|
if (!cb) {
|
|
42
49
|
// get, val is cb
|
|
43
50
|
cb = val;
|
|
44
51
|
val = undefined;
|
|
45
52
|
|
|
46
|
-
this._coll().findOne ({_id: this._id}, {projection: {paused: 1}}
|
|
47
|
-
|
|
53
|
+
this._coll().findOne ({_id: this._id}, {projection: {paused: 1}})
|
|
54
|
+
.then (res => {
|
|
48
55
|
debug ('mongo stats - paused: get %s -> %j', this._name, res);
|
|
49
56
|
cb (null, (res && res.paused) || false);
|
|
50
|
-
})
|
|
57
|
+
})
|
|
58
|
+
.catch (cb)
|
|
51
59
|
}
|
|
52
60
|
else {
|
|
53
61
|
// set
|
|
54
|
-
|
|
55
|
-
this._coll().updateOne ({_id: this._id}, upd, {upsert: true}
|
|
62
|
+
const upd = {$set: {paused : val}};
|
|
63
|
+
this._coll().updateOne ({_id: this._id}, upd, {upsert: true})
|
|
64
|
+
.then (res => {
|
|
56
65
|
debug ('mongo stats: updated %s -> %j', this._name, upd);
|
|
57
|
-
cb (
|
|
58
|
-
})
|
|
66
|
+
cb ();
|
|
67
|
+
})
|
|
68
|
+
.catch (cb);
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
|
|
73
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
62
74
|
_flush (cb) {
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
const upd = {$inc: {}};
|
|
76
|
+
let some_added = false;
|
|
65
77
|
|
|
66
78
|
_.forEach(this._cache, (value, key) => {
|
|
67
79
|
if (value) {
|
|
@@ -72,9 +84,13 @@ class MongoStats extends Stats {
|
|
|
72
84
|
});
|
|
73
85
|
|
|
74
86
|
if (some_added) {
|
|
75
|
-
this._coll().updateOne ({_id: this._id}, upd, {upsert: true}
|
|
87
|
+
this._coll().updateOne ({_id: this._id}, upd, {upsert: true})
|
|
88
|
+
.then (res => {
|
|
76
89
|
debug ('mongo stats: updated %s -> %j', this._name, upd);
|
|
77
|
-
if (cb) cb (
|
|
90
|
+
if (cb) cb ();
|
|
91
|
+
})
|
|
92
|
+
.catch (err => {
|
|
93
|
+
if (cb) return cb(err);
|
|
78
94
|
});
|
|
79
95
|
}
|
|
80
96
|
else {
|
|
@@ -83,6 +99,7 @@ class MongoStats extends Stats {
|
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
|
|
102
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
86
103
|
_ensureFlush() {
|
|
87
104
|
if (this._flusher) return;
|
|
88
105
|
|
|
@@ -93,6 +110,7 @@ class MongoStats extends Stats {
|
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
|
|
113
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
96
114
|
_cancelFlush() {
|
|
97
115
|
if (this._flusher) {
|
|
98
116
|
clearTimeout(this._flusher);
|
|
@@ -101,16 +119,19 @@ class MongoStats extends Stats {
|
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
|
|
122
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
104
123
|
_mongocl () {
|
|
105
124
|
return this._factory._mongocl;
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
|
|
128
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
109
129
|
_coll () {
|
|
110
130
|
return this._factory._coll;
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
|
|
134
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
114
135
|
incr(v, delta, cb) {
|
|
115
136
|
if ((delta === null) || (delta === undefined)) delta = 1;
|
|
116
137
|
|
|
@@ -125,48 +146,57 @@ class MongoStats extends Stats {
|
|
|
125
146
|
}
|
|
126
147
|
|
|
127
148
|
|
|
149
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
128
150
|
decr(v, delta, cb) {
|
|
129
151
|
if ((delta === null) || (delta === undefined)) delta = 1;
|
|
130
152
|
this.incr(v, -delta, cb);
|
|
131
153
|
}
|
|
132
154
|
|
|
155
|
+
|
|
156
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
133
157
|
opts (opts, cb) {
|
|
134
158
|
if (!cb) {
|
|
135
159
|
// get
|
|
136
160
|
cb = opts;
|
|
137
|
-
this._coll().findOne ({_id: this._id}, {projection: {opts: 1}}
|
|
138
|
-
|
|
161
|
+
this._coll().findOne ({_id: this._id}, {projection: {opts: 1}})
|
|
162
|
+
.then (res => {
|
|
139
163
|
debug ('mongo stats - opts: get %s -> %j', this._name, res);
|
|
140
164
|
cb (null, (res && res.opts) || {});
|
|
141
|
-
})
|
|
165
|
+
})
|
|
166
|
+
.catch (cb);
|
|
142
167
|
}
|
|
143
168
|
else {
|
|
144
169
|
// set
|
|
145
|
-
|
|
146
|
-
this._coll().updateOne ({_id: this._id}, upd, {upsert: true}
|
|
170
|
+
const upd = {$set: {opts : opts}};
|
|
171
|
+
this._coll().updateOne ({_id: this._id}, upd, {upsert: true})
|
|
172
|
+
.then (res => {
|
|
147
173
|
debug ('mongo stats: updated %s -> %j', this._name, upd);
|
|
148
|
-
cb (
|
|
149
|
-
})
|
|
174
|
+
cb ();
|
|
175
|
+
})
|
|
176
|
+
.catch (cb);
|
|
150
177
|
}
|
|
151
178
|
}
|
|
152
179
|
|
|
180
|
+
|
|
181
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
153
182
|
clear(cb) {
|
|
154
183
|
this._cancelFlush();
|
|
155
184
|
this._cache = {};
|
|
156
185
|
|
|
157
|
-
|
|
186
|
+
const upd = {
|
|
158
187
|
$unset: {
|
|
159
188
|
counters: 1,
|
|
160
189
|
opts: 1
|
|
161
190
|
}
|
|
162
191
|
};
|
|
163
192
|
|
|
164
|
-
this._coll().updateOne ({_id: this._id}, upd
|
|
165
|
-
|
|
166
|
-
|
|
193
|
+
this._coll().updateOne ({_id: this._id}, upd)
|
|
194
|
+
.then (() => cb ())
|
|
195
|
+
.catch(cb);
|
|
167
196
|
}
|
|
168
197
|
|
|
169
198
|
|
|
199
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
170
200
|
close (cb) {
|
|
171
201
|
this._cancelFlush();
|
|
172
202
|
this._flush (cb);
|
|
@@ -175,6 +205,7 @@ class MongoStats extends Stats {
|
|
|
175
205
|
|
|
176
206
|
|
|
177
207
|
|
|
208
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
178
209
|
class MongoStatsFactory {
|
|
179
210
|
constructor(cl, coll, opts) {
|
|
180
211
|
this._opts = opts || {};
|
|
@@ -190,6 +221,7 @@ class MongoStatsFactory {
|
|
|
190
221
|
type() { return MongoStatsFactory.Type() }
|
|
191
222
|
|
|
192
223
|
|
|
224
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
193
225
|
stats(ns, name, opts) {
|
|
194
226
|
var k = name + '@' + ns;
|
|
195
227
|
if (!this._instances [k]) {
|
|
@@ -200,6 +232,7 @@ class MongoStatsFactory {
|
|
|
200
232
|
return this._instances [k];
|
|
201
233
|
}
|
|
202
234
|
|
|
235
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
203
236
|
queues (ns, opts, cb) {
|
|
204
237
|
if (!cb) {
|
|
205
238
|
cb = opts;
|
|
@@ -207,11 +240,11 @@ class MongoStatsFactory {
|
|
|
207
240
|
}
|
|
208
241
|
|
|
209
242
|
if (opts.full) {
|
|
210
|
-
this._coll.find({_id: {$regex: '^keuss:stats:' + ns}}).
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
arr.forEach (
|
|
243
|
+
this._coll.find({_id: {$regex: '^keuss:stats:' + ns}}).
|
|
244
|
+
toArray ()
|
|
245
|
+
.then (arr => {
|
|
246
|
+
const res = {};
|
|
247
|
+
arr.forEach (elem => {
|
|
215
248
|
res [elem.name] = {
|
|
216
249
|
ns: elem.ns,
|
|
217
250
|
name: elem.name,
|
|
@@ -222,24 +255,25 @@ class MongoStatsFactory {
|
|
|
222
255
|
});
|
|
223
256
|
|
|
224
257
|
cb (null, res);
|
|
225
|
-
})
|
|
258
|
+
})
|
|
259
|
+
.catch (cb);
|
|
226
260
|
}
|
|
227
261
|
else {
|
|
228
|
-
this._coll.find({_id: {$regex: '^keuss:stats:' + ns}})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
|
|
262
|
+
this._coll.find({_id: {$regex: '^keuss:stats:' + ns}})
|
|
263
|
+
.project ({_id: 1, name: 1})
|
|
264
|
+
.toArray ()
|
|
265
|
+
.then (arr => {
|
|
266
|
+
const res = [];
|
|
267
|
+
arr.forEach (elem => res.push (elem.name));
|
|
236
268
|
cb (null, res);
|
|
237
|
-
})
|
|
269
|
+
})
|
|
270
|
+
.catch (cb);
|
|
238
271
|
}
|
|
239
272
|
}
|
|
240
273
|
|
|
274
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
241
275
|
close (cb) {
|
|
242
|
-
|
|
276
|
+
const tasks = [];
|
|
243
277
|
|
|
244
278
|
// flush pending stats
|
|
245
279
|
_.each (this._instances, (v, k) => {
|
|
@@ -253,12 +287,15 @@ class MongoStatsFactory {
|
|
|
253
287
|
(cb) => async.parallel (tasks, cb),
|
|
254
288
|
(cb) => {
|
|
255
289
|
debug (`closing MongoStatsFactory mongodb conn`);
|
|
256
|
-
this._mongocl.close (
|
|
290
|
+
this._mongocl.close ()
|
|
291
|
+
.then (res => cb (null, res))
|
|
292
|
+
.catch (cb);
|
|
257
293
|
}
|
|
258
294
|
], cb);
|
|
259
295
|
}
|
|
260
296
|
}
|
|
261
297
|
|
|
298
|
+
//////////////////////////////////////////////////////////////////////////////////
|
|
262
299
|
function creator (opts, cb) {
|
|
263
300
|
if (!cb) {
|
|
264
301
|
cb = opts;
|
|
@@ -274,13 +311,13 @@ function creator (opts, cb) {
|
|
|
274
311
|
|
|
275
312
|
debug ('initializing creator of MongoStatsFactory, connecting to %s', m_url);
|
|
276
313
|
|
|
277
|
-
MongoClient.connect (m_url
|
|
278
|
-
|
|
279
|
-
|
|
314
|
+
MongoClient.connect (m_url)
|
|
315
|
+
.then (cl => {
|
|
280
316
|
debug ('initializing creator of MongoStatsFactory, connected to %s', m_url);
|
|
281
317
|
var coll = cl.db().collection (m_coll);
|
|
282
318
|
cb (null, new MongoStatsFactory (cl, coll, opts));
|
|
283
|
-
})
|
|
319
|
+
})
|
|
320
|
+
.catch (cb);
|
|
284
321
|
}
|
|
285
322
|
|
|
286
323
|
module.exports = creator;
|
|
@@ -7,6 +7,13 @@ const _s_lua_code_push = `
|
|
|
7
7
|
-- mature-t in ARGV[2]
|
|
8
8
|
-- val in ARGV[3]
|
|
9
9
|
|
|
10
|
+
-- check id exists
|
|
11
|
+
local exists = redis.call ('HGET', 'keuss:q:ordered_queue:hash:' .. KEYS[1], ARGV[1])
|
|
12
|
+
|
|
13
|
+
if (exists) then
|
|
14
|
+
return redis.error_reply('EDUP duplicated _id ' .. ARGV[1])
|
|
15
|
+
end
|
|
16
|
+
|
|
10
17
|
-- insert obj in hash by id
|
|
11
18
|
redis.call ('HSET', 'keuss:q:ordered_queue:hash:' .. KEYS[1], ARGV[1], ARGV[3])
|
|
12
19
|
|
|
@@ -174,20 +181,33 @@ class RedisOrderedQueue {
|
|
|
174
181
|
push (entry, done) {
|
|
175
182
|
//////////////////////////////////
|
|
176
183
|
var pl = {
|
|
177
|
-
_id: entry.
|
|
184
|
+
_id: entry._id || uuid.v4(),
|
|
178
185
|
payload: entry.payload,
|
|
179
186
|
tries: entry.tries,
|
|
180
187
|
hdrs: entry.hdrs || {},
|
|
181
188
|
mature: (entry.mature || new Date ()).getTime ()
|
|
182
189
|
};
|
|
183
190
|
|
|
184
|
-
|
|
185
191
|
if (Buffer.isBuffer (pl.payload)) {
|
|
186
192
|
pl.payload = pl.payload.toString ('base64');
|
|
187
193
|
pl.type = 'buffer';
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
this._rediscl.roq_push (this._name, pl._id, pl.mature, JSON.stringify (pl),
|
|
196
|
+
this._rediscl.roq_push (this._name, pl._id, pl.mature, JSON.stringify (pl), (err, res) => {
|
|
197
|
+
if (err) {
|
|
198
|
+
if (err.message.startsWith ('EDUP ')) {
|
|
199
|
+
const e = new Error (`duplicated entry with _id ${pl._id}`)
|
|
200
|
+
e.code = 'EDUP';
|
|
201
|
+
done(e);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
done (err)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
done (null, res);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
191
211
|
}
|
|
192
212
|
|
|
193
213
|
|