keuss 1.7.4 → 2.0.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.
Files changed (65) hide show
  1. package/Pipeline/Queue.js +0 -1
  2. package/QFactory.js +11 -6
  3. package/Queue.js +1 -4
  4. package/README.md +2 -1
  5. package/TODO +0 -9
  6. package/backends/bucket-mongo-safe.js +11 -11
  7. package/backends/intraorder.js +13 -11
  8. package/backends/mongo.js +12 -9
  9. package/backends/pl-mongo.js +8 -5
  10. package/backends/postgres.js +380 -0
  11. package/backends/ps-mongo.js +11 -8
  12. package/backends/redis-list.js +8 -8
  13. package/backends/redis-oq.js +8 -3
  14. package/backends/stream-mongo.js +10 -15
  15. package/package.json +12 -8
  16. package/stats/mem.js +0 -1
  17. package/.github/workflows/codeql-analysis.yml +0 -72
  18. package/bench/all-mongo.js +0 -108
  19. package/bench/multi-q.js +0 -85
  20. package/bench/redis-oq-consumer-producer.js +0 -52
  21. package/bench/redis-oq-consumer.js +0 -40
  22. package/bench/redis-oq-producer.js +0 -43
  23. package/docker-compose/docker-compose.yaml +0 -18
  24. package/docker-compose.yaml +0 -18
  25. package/examples/pipelines/builder/index.js +0 -99
  26. package/examples/pipelines/fromRecipe/index.js +0 -127
  27. package/examples/pipelines/simplest/index.js +0 -38
  28. package/examples/pipelines/simulation-fork/index.js +0 -115
  29. package/examples/snippets/01-simplest-pop-push.js +0 -49
  30. package/examples/snippets/02-simplest-reserve-rollback-commit.js +0 -44
  31. package/examples/snippets/03-simplest-producer-consumer-loops.js +0 -77
  32. package/examples/snippets/04-bucket-mongo-safe-insert-reserve-commit.js +0 -78
  33. package/examples/snippets/05-insert-reserve-rollback-deadletter.js +0 -105
  34. package/examples/snippets/06-random-consumer-producer.js +0 -270
  35. package/examples/snippets/07-stream-simple.js +0 -53
  36. package/examples/snippets/redislabs-consumer-producer.js +0 -44
  37. package/examples/snippets/with-redis-stats-and-signaller-consumer-producer.js +0 -52
  38. package/examples/webhooks/README.md +0 -36
  39. package/examples/webhooks/app.js +0 -70
  40. package/examples/webhooks/consumer.js +0 -98
  41. package/examples/webhooks/index.js +0 -55
  42. package/examples/webhooks/package-lock.json +0 -500
  43. package/examples/webhooks/package.json +0 -23
  44. package/examples/webhooks/wh-payload.json +0 -38
  45. package/playground/irc.js +0 -53
  46. package/playground/pl-rollback.js +0 -55
  47. package/playground/pl1.js +0 -74
  48. package/playground/q1.js +0 -34
  49. package/playground/simple-pl.js +0 -42
  50. package/playground/stream-loops.js +0 -114
  51. package/test/backends_bucket-at-least-once.js +0 -302
  52. package/test/backends_deadletter.js +0 -227
  53. package/test/backends_payload.js +0 -542
  54. package/test/backends_push-pop.js +0 -170
  55. package/test/backends_remove.js +0 -320
  56. package/test/backends_reserve-commit-rollback.js +0 -1033
  57. package/test/intraorder.js +0 -325
  58. package/test/pause.js +0 -220
  59. package/test/pipeline-Builder.js +0 -285
  60. package/test/pipeline-ChoiceLink.js +0 -241
  61. package/test/pipeline-DirectLink.js +0 -376
  62. package/test/pipeline-Sink.js +0 -175
  63. package/test/signal.js +0 -196
  64. package/test/stats.js +0 -296
  65. package/test/stream-mongo.js +0 -166
package/Pipeline/Queue.js CHANGED
@@ -37,7 +37,6 @@ class PipelinedMongoQueue extends Queue {
37
37
 
38
38
  this._col.insertOne (entry, {}, (err, result) => {
39
39
  if (err) return callback (err);
40
- // TODO result.insertedCount must be 1
41
40
  callback (null, result.insertedId);
42
41
  });
43
42
  }
package/QFactory.js CHANGED
@@ -36,12 +36,17 @@ class QFactory {
36
36
  this._stats_factory = res[1];
37
37
 
38
38
  if (this._opts.deadletter) {
39
- this._deadletter_queue = this.queue (this._opts.deadletter.queue || '__deadletter__');
40
39
  this._max_ko = this._opts.deadletter.max_ko || 0;
41
- debug('%s: uses deadletter queue %s, max KO is %d', this._name, this._deadletter_queue.name(), this._max_ko);
40
+ const qname = this._opts.deadletter.queue || '__deadletter__';
41
+ debug('%s: uses deadletter queue %s, max KO is %d', this._name, qname, this._max_ko);
42
+ this.queue (qname, (err, dlq) => {
43
+ this._deadletter_queue = dlq;
44
+ cb (err);
45
+ });
46
+ }
47
+ else {
48
+ cb();
42
49
  }
43
-
44
- cb();
45
50
  });
46
51
  }
47
52
 
@@ -53,8 +58,8 @@ class QFactory {
53
58
  return this._stats_factory;
54
59
  }
55
60
 
56
- queue (name, opts) {
57
- return null;
61
+ queue (name, opts, cb) {
62
+ return cb ();
58
63
  }
59
64
 
60
65
  close (cb) {
package/Queue.js CHANGED
@@ -342,8 +342,6 @@ class Queue {
342
342
  //////////////////////////////////
343
343
  // obtain element from queue
344
344
  pop (cid, opts, callback) {
345
- // TODO fail if too many consumers?
346
-
347
345
  if (this._drained) return setImmediate (() => {
348
346
  debug ('%s: pop while drained, return error', this._name);
349
347
  if (callback) callback ('drain');
@@ -461,7 +459,7 @@ class Queue {
461
459
  if (
462
460
  (obj.tries) && // only if we got tries
463
461
  (this._factory.deadletter_queue ()) && // AND the factory has a deadletter queue
464
- (this._factory.max_ko ()) && // AND thee's a max ko attempts
462
+ (this._factory.max_ko ()) && // AND there's a max ko attempts
465
463
  (obj.tries > this._factory.max_ko ()) && // AND we got enough tries
466
464
  (this.name () != '__deadletter__') // and this queue is not deadletter already
467
465
  ) {
@@ -592,7 +590,6 @@ class Queue {
592
590
  }
593
591
  }
594
592
 
595
- // TODO eat up errors?
596
593
  if (err) {
597
594
  // get/reserve in error
598
595
  debug ('%s - tid %s: getOrReserve_cb in error: err %o', this._name, consumer.tid, err);
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Keuss
2
- Enterprise-grade Job Queues for node.js, backed by redis and/or MongoDB
2
+
3
+ Enterprise-grade Job Queues for node.js backed by redis, MongoDB or PostgreSQL
3
4
 
4
5
  * [Quickstart](https://pepmartinez.github.io/keuss/docs/quickstart)
5
6
  * [Documentation](https://pepmartinez.github.io/keuss/docs/)
package/TODO CHANGED
@@ -8,16 +8,7 @@ prio 1
8
8
  * pipelines
9
9
  + add some commonplace transforms (etl-like)
10
10
  + create a server for that, allow processor code defined externally
11
- * documentation
12
- + blog entries:
13
- - motivations on using mongodb+redis. comparatives, pros&cons
14
- - buckets
15
- - pipelines
16
- - tape
17
- - redis-oq
18
- - exotic experiments: intraorder
19
11
 
20
12
  prio 0
21
13
  -----------------------------------------------------------------
22
- * bridges?
23
14
  * signal queue creation or read queues on refresh?
@@ -96,8 +96,6 @@ class Bucket {
96
96
  elem.mature = this._mature;
97
97
  elem._id = this.id () + ':' + i;
98
98
 
99
- // TODO optimization: set this._b[i] = null
100
-
101
99
  if (is_reserve) {
102
100
  this._b_states[i] = State.Reserved;
103
101
  this._b_counts.Reserved++;
@@ -314,7 +312,6 @@ class BucketSet {
314
312
  if (bcket.exhausted ()) {
315
313
  debug ('bucket %s already exhausted, read another', bcket.id());
316
314
  this._active_bucket = null;
317
- // TODO loop internally!!!!!
318
315
  cb ();
319
316
  }
320
317
  else {
@@ -523,9 +520,7 @@ class BucketSet {
523
520
 
524
521
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
525
522
  class BucketMongoSafeQueue extends Queue {
526
-
527
523
  /*
528
-
529
524
  options:
530
525
  bucket_max_size || 1024;
531
526
  bucket_max_wait || 500;
@@ -540,9 +535,7 @@ class BucketMongoSafeQueue extends Queue {
540
535
  //////////////////////////////////////////////
541
536
  super (name, factory, opts, orig_opts);
542
537
 
543
- this._factory = factory;
544
538
  this._col = factory._db.collection (name);
545
- this._ensureIndexes (err => {});
546
539
 
547
540
  this._insert_bucket = {
548
541
  _id: new mongo.ObjectID (),
@@ -821,7 +814,7 @@ class BucketMongoSafeQueue extends Queue {
821
814
 
822
815
  /////////////////////////////////////////
823
816
  _ensureIndexes (cb) {
824
- this._col.createIndex ({mature : 1}, err => cb (err));
817
+ this._col.createIndex ({mature : 1}, err => cb (err, this));
825
818
  }
826
819
 
827
820
 
@@ -915,10 +908,17 @@ class Factory extends QFactory_MongoDB_defaults {
915
908
  this._db = mongo_conn.db();
916
909
  }
917
910
 
918
- queue (name, opts) {
919
- var full_opts = {}
911
+ queue (name, opts, cb) {
912
+ if (!cb) {
913
+ cb = opts;
914
+ opts = {};
915
+ }
916
+
917
+ const full_opts = {};
920
918
  _.merge(full_opts, this._opts, opts);
921
- return new BucketMongoSafeQueue (name, this, full_opts, opts);
919
+
920
+ const q = new BucketMongoSafeQueue (name, this, full_opts, opts);
921
+ q._ensureIndexes (cb);
922
922
  }
923
923
 
924
924
  close (cb) {
@@ -12,14 +12,10 @@ const debug = require('debug')('keuss:Queue:intraqueue');
12
12
 
13
13
 
14
14
  class IntraOrderedQueue extends Queue {
15
-
16
15
  //////////////////////////////////////////////
17
16
  constructor (name, factory, opts, orig_opts) {
18
17
  super (name, factory, opts, orig_opts);
19
-
20
- this._factory = factory;
21
18
  this._col = factory._db.collection (name);
22
- this.ensureIndexes (err => {});
23
19
  }
24
20
 
25
21
 
@@ -49,7 +45,6 @@ class IntraOrderedQueue extends Queue {
49
45
  this._col.updateOne (q, upd, opts, (err, result) => {
50
46
  debug ('insert: updateOne (%j, %j, %j) => (%j, %j)', q, upd, opts, err, result);
51
47
  if (err) return callback (err);
52
- // TODO result.insertedCount must be 1
53
48
  callback (null, result.upsertedId || q._id);
54
49
  });
55
50
  }
@@ -308,10 +303,10 @@ class IntraOrderedQueue extends Queue {
308
303
 
309
304
  //////////////////////////////////////////////////////////////////
310
305
  // create needed indexes for O(1) functioning
311
- ensureIndexes (cb) {
306
+ _ensureIndexes (cb) {
312
307
  async.series ([
313
- cb => this._col.createIndex ({mature : 1, qcnt: 1}, cb),
314
- ], cb);
308
+ cb => this._col.createIndex ({mature : 1, qcnt: 1}, err => cb (err)),
309
+ ], err => cb (err, this));
315
310
  }
316
311
  }
317
312
 
@@ -323,10 +318,17 @@ class Factory extends QFactory_MongoDB_defaults {
323
318
  this._db = mongo_conn.db();
324
319
  }
325
320
 
326
- queue (name, opts) {
327
- var full_opts = {};
321
+ queue (name, opts, cb) {
322
+ if (!cb) {
323
+ cb = opts;
324
+ opts = {};
325
+ }
326
+
327
+ const full_opts = {};
328
328
  _.merge(full_opts, this._opts, opts);
329
- return new IntraOrderedQueue (name, this, full_opts, opts);
329
+
330
+ const q = new IntraOrderedQueue (name, this, full_opts, opts);
331
+ q._ensureIndexes (cb);
330
332
  }
331
333
 
332
334
  close (cb) {
package/backends/mongo.js CHANGED
@@ -11,10 +11,7 @@ class SimpleMongoQueue extends Queue {
11
11
  //////////////////////////////////////////////
12
12
  constructor (name, factory, opts, orig_opts) {
13
13
  super (name, factory, opts, orig_opts);
14
-
15
- this._factory = factory;
16
14
  this._col = factory._db.collection (name);
17
- this.ensureIndexes (function (err) {});
18
15
  }
19
16
 
20
17
 
@@ -33,7 +30,6 @@ class SimpleMongoQueue extends Queue {
33
30
  insert (entry, callback) {
34
31
  this._col.insertOne (entry, {}, (err, result) => {
35
32
  if (err) return callback (err);
36
- // TODO result.insertedCount must be 1
37
33
  callback (null, result.insertedId);
38
34
  });
39
35
  }
@@ -213,8 +209,8 @@ class SimpleMongoQueue extends Queue {
213
209
 
214
210
  //////////////////////////////////////////////////////////////////
215
211
  // create needed indexes for O(1) functioning
216
- ensureIndexes (cb) {
217
- this._col.createIndex ({mature : 1}, err => cb (err));
212
+ _ensureIndexes (cb) {
213
+ this._col.createIndex ({mature : 1}, err => cb (err, this));
218
214
  }
219
215
  };
220
216
 
@@ -226,10 +222,17 @@ class Factory extends QFactory_MongoDB_defaults {
226
222
  this._db = mongo_conn.db();
227
223
  }
228
224
 
229
- queue (name, opts) {
230
- var full_opts = {};
225
+ queue (name, opts, cb) {
226
+ if (!cb) {
227
+ cb = opts;
228
+ opts = {};
229
+ }
230
+
231
+ const full_opts = {};
231
232
  _.merge(full_opts, this._opts, opts);
232
- return new SimpleMongoQueue (name, this, full_opts, opts);
233
+
234
+ const q = new SimpleMongoQueue (name, this, full_opts, opts);
235
+ q._ensureIndexes (cb);
233
236
  }
234
237
 
235
238
  close (cb) {
@@ -119,18 +119,21 @@ class Factory extends QFactory_MongoDB_defaults {
119
119
 
120
120
 
121
121
  ///////////////////////////////////////////////////////////
122
- queue (name, opts) {
123
- if (!opts) opts = {};
122
+ queue (name, opts, cb) {
123
+ if (!cb) {
124
+ cb = opts;
125
+ opts = {};
126
+ }
124
127
 
125
- var pl_name = opts.pipeline || 'default';
126
- var pipeline = this._pipelines[pl_name];
128
+ const pl_name = opts.pipeline || 'default';
129
+ let pipeline = this._pipelines[pl_name];
127
130
 
128
131
  if (!pipeline) {
129
132
  this._pipelines[pl_name] = new Pipeline (pl_name, this);
130
133
  pipeline = this._pipelines[pl_name];
131
134
  }
132
135
 
133
- return this._queue_from_pipeline (name, pipeline, opts);
136
+ return setImmediate (() => cb (null, this._queue_from_pipeline (name, pipeline, opts)));
134
137
  }
135
138
 
136
139
 
@@ -0,0 +1,380 @@
1
+ const _ = require ('lodash');
2
+ const uuid = require ('uuid');
3
+ const pg = require ('pg');
4
+
5
+ const Queue = require ('../Queue');
6
+ const QFactory = require ('../QFactory');
7
+
8
+ const debug = require('debug')('keuss:Queue:postgres');
9
+
10
+ class PGQueue extends Queue {
11
+
12
+
13
+ // TODO escape table name properly , as identifier, and check for reserved names using pg_get_keywords(), if the name is in select * from pg_get_keywords() where catdesc = 'R', then it should not be used
14
+ //////////////////////////////////////////////
15
+ constructor (name, factory, opts, orig_opts) {
16
+ super (name, factory, opts, orig_opts);
17
+ this._pool = factory._pool;
18
+ this._tbl_name = this._name;
19
+ }
20
+
21
+
22
+ /////////////////////////////////////////
23
+ static Type () {
24
+ return 'postgres:simple';
25
+ }
26
+
27
+
28
+ /////////////////////////////////////////
29
+ type () {
30
+ return 'postgres:simple';
31
+ }
32
+
33
+
34
+ //////////////////////////////////////////////
35
+ // ensure table and indexes exists
36
+ _init (cb) {
37
+ this._pool.query (`
38
+ CREATE TABLE IF NOT EXISTS ${this._tbl_name} (
39
+ _id VARCHAR(64) PRIMARY KEY,
40
+ _pl JSONB,
41
+ mature TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
42
+ tries INTEGER DEFAULT 0 NOT NULL,
43
+ reserved TIMESTAMPTZ
44
+ );
45
+ CREATE INDEX IF NOT EXISTS idx_${this._tbl_name}_mature ON ${this._tbl_name} (mature);
46
+ `, err => cb (err));
47
+ }
48
+
49
+
50
+ /////////////////////////////////////////
51
+ // add element to queue
52
+ insert (entry, cb) {
53
+ const _id = entry.id || uuid.v4();
54
+ const tries = entry.tries || 0;
55
+ const mature = new Date (entry.mature);
56
+
57
+ const pl = {
58
+ payload: entry.payload,
59
+ hdrs: entry.hdrs || {}
60
+ }
61
+
62
+ if (Buffer.isBuffer (pl.payload)) {
63
+ pl.payload = pl.payload.toString ('base64');
64
+ pl.type = 'buffer';
65
+ }
66
+
67
+ this._pool.query (`INSERT INTO ${this._tbl_name} VALUES($1, $2, $3, $4)`, [_id, pl, mature, tries], (err, res) => {
68
+ if (err) return cb (err);
69
+ cb (null, _id)
70
+ })
71
+ }
72
+
73
+
74
+ /////////////////////////////////////////
75
+ // get element from queue
76
+ get (cb) {
77
+ this._pool.query (`
78
+ DELETE FROM ${this._tbl_name}
79
+ WHERE _id = (
80
+ SELECT _id
81
+ FROM ${this._tbl_name}
82
+ WHERE mature < now()
83
+ ORDER BY mature
84
+ FOR UPDATE SKIP LOCKED
85
+ LIMIT 1
86
+ )
87
+ RETURNING *;
88
+ `, (err, res) => {
89
+ if (err) return cb (err);
90
+
91
+ if (_.size (res.rows) == 0) return cb (null, null); // not found
92
+
93
+ const pl = res.rows[0];
94
+
95
+ _.merge (pl, pl._pl);
96
+ delete (pl._pl);
97
+
98
+ if (pl.type == 'buffer') {
99
+ try {
100
+ pl.payload = Buffer.from (pl.payload, 'base64');
101
+ } catch (e) {
102
+ // TODO maybe log some error/Statistic?
103
+ }
104
+ }
105
+
106
+ cb (null, pl);
107
+ })
108
+ }
109
+
110
+
111
+ //////////////////////////////////
112
+ // reserve element: call cb (err, pl) where pl has an id
113
+ reserve (cb) {
114
+ const delay = this._opts.reserve_delay || 120;
115
+
116
+ this._pool.query (`
117
+ UPDATE ${this._tbl_name}
118
+ SET
119
+ tries = tries + 1,
120
+ mature = now() + make_interval(secs => ${delay}),
121
+ reserved = now()
122
+ WHERE _id = (
123
+ SELECT _id
124
+ FROM ${this._tbl_name}
125
+ WHERE mature < now()
126
+ ORDER BY mature
127
+ FOR UPDATE SKIP LOCKED
128
+ LIMIT 1
129
+ )
130
+ RETURNING *;
131
+ `, (err, res) => {
132
+ if (err) return cb (err);
133
+
134
+ if (_.size (res.rows) == 0) return cb (null, null); // not found
135
+
136
+ const pl = res.rows[0];
137
+
138
+ _.merge (pl, pl._pl);
139
+ delete (pl._pl);
140
+
141
+ // adjust tries to pre-update
142
+ pl.tries--;
143
+
144
+ if (pl.type == 'buffer') {
145
+ try {
146
+ pl.payload = Buffer.from (pl.payload, 'base64');
147
+ } catch (e) {
148
+ // TODO maybe log some error/Statistic?
149
+ }
150
+ }
151
+
152
+ cb (null, pl);
153
+ })
154
+ }
155
+
156
+
157
+ //////////////////////////////////
158
+ // commit previous reserve, by p.id
159
+ commit (id, cb) {
160
+ if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as commit id: not a valid UUID');
161
+
162
+ this._pool.query (`
163
+ DELETE FROM ${this._tbl_name}
164
+ WHERE _id = $1
165
+ AND reserved IS NOT NULL
166
+ `,
167
+ [id],
168
+ (err, res) => {
169
+ if (err) return cb (err);
170
+ cb (null, res && (res.rowCount == 1));
171
+ })
172
+ }
173
+
174
+
175
+ //////////////////////////////////
176
+ // rollback previous reserve, by p.id
177
+ rollback (id, next_t, cb) {
178
+ if (_.isFunction (next_t)) {
179
+ cb = next_t;
180
+ next_t = null;
181
+ }
182
+
183
+ if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as rollback id: not a valid UUID');
184
+
185
+ const nxt = (next_t ? new Date (next_t) : Queue.now ());
186
+
187
+ this._pool.query (`
188
+ UPDATE ${this._tbl_name}
189
+ SET
190
+ reserved = NULL,
191
+ mature = $1
192
+ WHERE _id = $2
193
+ AND reserved IS NOT NULL
194
+ `,
195
+ [nxt, id],
196
+ (err, res) => {
197
+ if (err) return cb (err);
198
+ cb (null, res && (res.rowCount == 1));
199
+ })
200
+ }
201
+
202
+
203
+ //////////////////////////////////
204
+ // queue size including non-mature elements
205
+ totalSize (cb) {
206
+ this._pool.query (`
207
+ SELECT COUNT(*)
208
+ FROM ${this._tbl_name}
209
+ `,
210
+ (err, res) => {
211
+ if (err) return cb (err);
212
+ cb (null, parseInt (res.rows[0].count));
213
+ })
214
+ }
215
+
216
+
217
+ //////////////////////////////////
218
+ // queue size NOT including non-mature elements
219
+ size (cb) {
220
+ this._pool.query (`
221
+ SELECT COUNT(*)
222
+ FROM ${this._tbl_name}
223
+ WHERE mature < now()
224
+ `,
225
+ (err, res) => {
226
+ if (err) return cb (err);
227
+ cb (null, parseInt (res.rows[0].count));
228
+ })
229
+ }
230
+
231
+
232
+ //////////////////////////////////
233
+ // queue size of non-mature elements only
234
+ schedSize (cb) {
235
+ this._pool.query (`
236
+ SELECT COUNT(*)
237
+ FROM ${this._tbl_name}
238
+ WHERE mature >= now()
239
+ AND reserved IS NULL
240
+ `,
241
+ (err, res) => {
242
+ if (err) return cb (err);
243
+ cb (null, parseInt (res.rows[0].count));
244
+ })
245
+ }
246
+
247
+
248
+ //////////////////////////////////
249
+ // queue size of reserved elements only
250
+ resvSize (cb) {
251
+ this._pool.query (`
252
+ SELECT COUNT(*)
253
+ FROM ${this._tbl_name}
254
+ WHERE mature >= now()
255
+ AND reserved IS NOT NULL
256
+ `,
257
+ (err, res) => {
258
+ if (err) return cb (err);
259
+ cb (null, parseInt (res.rows[0].count));
260
+ })
261
+ }
262
+
263
+
264
+ /////////////////////////////////////////
265
+ // get element from queue
266
+ next_t (cb) {
267
+ this._pool.query (`
268
+ SELECT mature
269
+ FROM ${this._tbl_name}
270
+ ORDER BY mature
271
+ LIMIT 1
272
+ `, (err, res) => {
273
+ if (err) return cb (err);
274
+ if (_.size (res.rows) == 0) return cb (null, null); // not found
275
+
276
+ cb (null, res.rows[0].mature);
277
+ });
278
+ }
279
+
280
+
281
+ //////////////////////////////////////////////
282
+ // remove by id
283
+ remove (id, cb) {
284
+ if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as remove id: not a valid UUID');
285
+
286
+ this._pool.query (`
287
+ DELETE FROM ${this._tbl_name}
288
+ WHERE _id = $1
289
+ AND reserved IS NULL
290
+ `,
291
+ [id],
292
+ (err, res) => {
293
+ if (err) return cb (err);
294
+ cb (null, res && (res.rowCount == 1));
295
+ })
296
+ }
297
+
298
+
299
+ ///////////////////////////////////////////////////////////////////////////////
300
+ // private parts
301
+
302
+ };
303
+
304
+
305
+
306
+ ///////////////////////////////////////////////////////////////////////////////
307
+ class Factory extends QFactory {
308
+ constructor (opts, pg_pool) {
309
+ super (opts);
310
+ this._pool = pg_pool;
311
+ debug ('crated Factory with options %j', opts);
312
+ }
313
+
314
+ queue (name, opts, cb) {
315
+ if (!cb) {
316
+ cb = opts;
317
+ opts = {};
318
+ }
319
+
320
+ const full_opts = {};
321
+ _.merge(full_opts, this._opts, opts);
322
+ debug ('creating queue with full_opts %j', full_opts);
323
+
324
+ const q = new PGQueue (name, this, full_opts, opts);
325
+ q._init (err => {
326
+ if (err) return cb (err);
327
+ cb (null, q);
328
+ });
329
+ }
330
+
331
+ close (cb) {
332
+ super.close (() => {
333
+ debug ('closing pool...');
334
+ this._pool.end (cb);
335
+ });
336
+ }
337
+
338
+ type () {
339
+ return PGQueue.Type ();
340
+ }
341
+
342
+ capabilities () {
343
+ return {
344
+ sched: true,
345
+ reserve: true,
346
+ pipeline: false,
347
+ tape: false,
348
+ remove: true
349
+ };
350
+ }
351
+ }
352
+
353
+
354
+ ///////////////////////////////////////////////////////////////////////////////
355
+ function creator (opts, cb) {
356
+ const _opts = opts || {};
357
+
358
+ debug ('Creator: creating pool with %j', _opts);
359
+
360
+ const dflt = {
361
+ user: 'pg',
362
+ password: 'pg',
363
+ host: 'localhost',
364
+ port: 5432,
365
+ database: 'pg'
366
+ }
367
+
368
+ const pool = new pg.Pool(_.merge ({}, dflt, _opts.postgres));
369
+
370
+ // ensure the pool can connect
371
+ pool.query ('select 1', err => {
372
+ if (err) return cb (err);
373
+
374
+ debug ('pool created, can connect. Creating Factory...');
375
+ const F = new Factory (_opts, pool);
376
+ F.async_init (err => cb (err, F));
377
+ });
378
+ }
379
+
380
+ module.exports = creator;