liwi-mongo 8.3.1 → 9.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.
@@ -1,658 +0,0 @@
1
- import { ObjectID, MongoClient } from 'mongodb';
2
- import { AbstractStoreCursor, AbstractConnection } from 'liwi-store';
3
- import { AbstractSubscribableStoreQuery, SubscribeStore } from 'liwi-subscribe-store';
4
- import mingo from 'mingo';
5
- import Logger from 'nightingale-logger';
6
-
7
- class MongoCursor extends AbstractStoreCursor {
8
- // key in AbstractCursor
9
- constructor(store, cursor) {
10
- super(store);
11
- this.cursor = cursor;
12
- }
13
-
14
- advance(count) {
15
- this.cursor.skip(count);
16
- }
17
-
18
- next() {
19
- return this.cursor.next().then(value => {
20
- this._result = value;
21
- this.key = value === null || value === void 0 ? void 0 : value._id;
22
- return this.key;
23
- });
24
- }
25
-
26
- limit(newLimit) {
27
- this.cursor.limit(newLimit);
28
- return Promise.resolve(this);
29
- }
30
-
31
- count(applySkipLimit = false) {
32
- return this.cursor.count(applySkipLimit);
33
- }
34
-
35
- result() {
36
- if (!this._result) throw new Error('Cannot call result() before next()');
37
- return Promise.resolve(this._result);
38
- }
39
-
40
- close() {
41
- if (this.cursor) {
42
- this.cursor.close();
43
- }
44
-
45
- return Promise.resolve();
46
- }
47
-
48
- toArray() {
49
- return this.cursor.toArray();
50
- }
51
-
52
- }
53
-
54
- /* eslint-disable complexity, max-lines */
55
-
56
- const identityTransformer = model => model;
57
-
58
- class MongoQueryCollection extends AbstractSubscribableStoreQuery {
59
- constructor(store, options, transformer = identityTransformer) {
60
- super();
61
- this.store = store;
62
- this.options = options;
63
- this.transformer = transformer;
64
- }
65
-
66
- createTestCriteria() {
67
- if (!this.testCriteria) {
68
- if (!this.options.criteria) {
69
- return () => true;
70
- }
71
-
72
- const mingoQuery = new mingo.Query(this.options.criteria);
73
- this.testCriteria = mingoQuery.test.bind(mingoQuery);
74
- }
75
-
76
- return this.testCriteria;
77
- }
78
-
79
- async fetch(onFulfilled) {
80
- const cursor = await this.createMongoCursor();
81
- const [result, count] = await Promise.all([cursor.toArray(), cursor.count()]);
82
- return onFulfilled({
83
- result: result.map(this.transformer),
84
- meta: {
85
- total: count
86
- },
87
- info: {
88
- sort: this.options.sort,
89
- limit: this.options.limit,
90
- keyPath: this.store.keyPath
91
- }
92
- });
93
- }
94
-
95
- _subscribe(callback, _includeInitial) {
96
- const store = super.getSubscribeStore();
97
- const testCriteria = this.createTestCriteria();
98
- const promise = _includeInitial ? this.fetch(({
99
- result,
100
- meta,
101
- info
102
- }) => {
103
- callback(null, [{
104
- type: 'initial',
105
- initial: result,
106
- queryInfo: info,
107
- meta
108
- }]);
109
- }) : Promise.resolve();
110
- const unsubscribe = store.subscribe(action => {
111
- const changes = [];
112
-
113
- switch (action.type) {
114
- case 'inserted':
115
- {
116
- const filtered = action.next.filter(testCriteria);
117
-
118
- if (filtered.length > 0) {
119
- changes.push({
120
- type: 'inserted',
121
- result: filtered.map(this.transformer)
122
- });
123
- }
124
-
125
- break;
126
- }
127
-
128
- case 'deleted':
129
- {
130
- const filtered = action.prev.filter(testCriteria);
131
-
132
- if (filtered.length > 0) {
133
- changes.push({
134
- type: 'deleted',
135
- keys: filtered.map(object => object[this.store.keyPath])
136
- });
137
- }
138
-
139
- break;
140
- }
141
-
142
- case 'updated':
143
- {
144
- const {
145
- deleted,
146
- updated,
147
- inserted
148
- } = action.changes.reduce((acc, [prevObject, nextObject]) => {
149
- if (testCriteria(prevObject)) {
150
- if (!testCriteria(nextObject)) {
151
- acc.deleted.push(prevObject[this.store.keyPath]);
152
- } else {
153
- acc.updated.push(this.transformer(nextObject));
154
- }
155
- } else if (testCriteria(nextObject)) {
156
- acc.inserted.push(this.transformer(nextObject));
157
- }
158
-
159
- return acc;
160
- }, {
161
- deleted: [],
162
- updated: [],
163
- inserted: []
164
- });
165
-
166
- if (deleted.length > 0) {
167
- changes.push({
168
- type: 'deleted',
169
- keys: deleted
170
- });
171
- }
172
-
173
- if (updated.length > 0) {
174
- changes.push({
175
- type: 'updated',
176
- result: updated
177
- });
178
- }
179
-
180
- if (inserted.length > 0) {
181
- changes.push({
182
- type: 'inserted',
183
- result: inserted
184
- });
185
- }
186
-
187
- break;
188
- }
189
-
190
- default:
191
- throw new Error('Unsupported type');
192
- }
193
-
194
- if (changes.length === 0) return;
195
- callback(null, changes);
196
- }); // let _feed;
197
- // const promise = this.queryCallback(this.store.query(), this.store.r)
198
- // .changes({
199
- // includeInitial: _includeInitial,
200
- // includeStates: true,
201
- // includeTypes: true,
202
- // includeOffsets: true,
203
- // })
204
- // .then((feed) => {
205
- // if (args.length === 0) {
206
- // _feed = feed;
207
- // delete this._promise;
208
- // }
209
- //
210
- // feed.each(callback);
211
- // return feed;
212
- // });
213
- //
214
- // if (args.length === 0) this._promise = promise;
215
-
216
- return {
217
- stop: unsubscribe,
218
- cancel: unsubscribe,
219
- then: (onFulfilled, onRejected) => promise.then(onFulfilled, onRejected)
220
- };
221
- }
222
-
223
- async createMongoCursor() {
224
- const cursor = await this.store.cursor(this.options.criteria, this.options.sort);
225
-
226
- if (this.options.skip) {
227
- cursor.advance(this.options.skip);
228
- }
229
-
230
- if (this.options.limit) {
231
- await cursor.limit(this.options.limit);
232
- }
233
-
234
- return cursor;
235
- }
236
-
237
- }
238
-
239
- /* eslint-disable complexity, max-lines */
240
-
241
- const identityTransformer$1 = model => model;
242
-
243
- class MongoQuerySingleItem extends AbstractSubscribableStoreQuery {
244
- constructor(store, options, transformer = identityTransformer$1) {
245
- super();
246
- this.store = store;
247
- this.options = options;
248
- this.transformer = transformer;
249
- }
250
-
251
- createMingoQuery() {
252
- if (!this.mingoQuery) {
253
- if (!this.options.criteria) {
254
- return {
255
- test: () => true
256
- };
257
- }
258
-
259
- this.mingoQuery = new mingo.Query(this.options.criteria);
260
- }
261
-
262
- return this.mingoQuery;
263
- }
264
-
265
- async fetch(onFulfilled) {
266
- const cursor = await this.createMongoCursor();
267
- await cursor.limit(1);
268
- return cursor.toArray().then(result => {
269
- const item = result.length === 0 ? null : this.transformer(result[0]);
270
- return onFulfilled({
271
- result: item,
272
- meta: {
273
- total: result === null ? 0 : 1
274
- },
275
- info: {
276
- limit: 1,
277
- keyPath: this.store.keyPath
278
- }
279
- });
280
- });
281
- }
282
-
283
- _subscribe(callback, _includeInitial) {
284
- const store = super.getSubscribeStore();
285
- const mingoQuery = this.createMingoQuery();
286
- const promise = _includeInitial ? this.fetch(({
287
- result,
288
- meta,
289
- info
290
- }) => {
291
- callback(null, [{
292
- type: 'initial',
293
- initial: result,
294
- queryInfo: info,
295
- meta
296
- }]);
297
- }) : Promise.resolve();
298
- const unsubscribe = store.subscribe(async action => {
299
- const changes = [];
300
-
301
- switch (action.type) {
302
- case 'inserted':
303
- {
304
- const filtered = action.next.filter(mingoQuery.test);
305
-
306
- if (filtered.length > 0) {
307
- changes.push({
308
- type: 'updated',
309
- result: this.transformer(filtered[0])
310
- });
311
- }
312
-
313
- break;
314
- }
315
-
316
- case 'deleted':
317
- {
318
- const filtered = action.prev.filter(mingoQuery.test);
319
-
320
- if (filtered.length > 0) {
321
- changes.push({
322
- type: 'deleted',
323
- keys: filtered.map(object => object[this.store.keyPath])
324
- });
325
- }
326
-
327
- break;
328
- }
329
-
330
- case 'updated':
331
- {
332
- const filtered = action.changes.filter(([prev, next]) => mingoQuery.test(prev));
333
-
334
- if (filtered.length > 0) {
335
- if (this.options.sort) {
336
- const {
337
- result
338
- } = await this.fetch(res => res);
339
- changes.push({
340
- type: 'updated',
341
- result
342
- });
343
- } else if (filtered.length !== 1) {
344
- throw new Error('should not match more than 1, use sort if you can have multiple match');
345
- } else {
346
- const [, next] = filtered[0];
347
- changes.push({
348
- type: 'updated',
349
- result: mingoQuery.test(next) ? this.transformer(next) : null
350
- });
351
- }
352
- } else if (filtered.length === 0) ;
353
-
354
- break;
355
- }
356
-
357
- default:
358
- throw new Error('Unsupported type');
359
- }
360
-
361
- if (changes.length === 0) return;
362
- callback(null, changes);
363
- });
364
- return {
365
- stop: unsubscribe,
366
- cancel: unsubscribe,
367
- then: (onFulfilled, onRejected) => promise.then(onFulfilled, onRejected)
368
- };
369
- }
370
-
371
- async createMongoCursor() {
372
- const cursor = await this.store.cursor(this.options.criteria, this.options.sort);
373
-
374
- if (this.options.limit) {
375
- await cursor.limit(this.options.limit);
376
- }
377
-
378
- return cursor;
379
- }
380
-
381
- }
382
-
383
- /* eslint-disable max-lines */
384
- class MongoStore {
385
- constructor(connection, collectionName) {
386
- this.keyPath = '_id';
387
- this.connection = connection;
388
-
389
- if (!collectionName) {
390
- throw new Error(`Invalid collectionName: "${collectionName}"`);
391
- }
392
-
393
- this._collection = connection.getConnection().then(client => {
394
- this._collection = client.db().collection(collectionName);
395
- return this._collection;
396
- }, err => {
397
- this._collection = Promise.reject(err);
398
- return this._collection;
399
- });
400
- }
401
-
402
- get collection() {
403
- if (this.connection.connectionFailed) {
404
- return Promise.reject(new Error('MongoDB connection failed'));
405
- }
406
-
407
- return Promise.resolve(this._collection);
408
- }
409
-
410
- createQuerySingleItem(options, transformer) {
411
- return new MongoQuerySingleItem(this, options, transformer);
412
- }
413
-
414
- createQueryCollection(options, transformer) {
415
- return new MongoQueryCollection(this, options, transformer);
416
- }
417
-
418
- async insertOne(object) {
419
- if (!object._id) {
420
- object._id = new ObjectID().toString();
421
- }
422
-
423
- if (!object.created) object.created = new Date();
424
- if (!object.updated) object.updated = new Date();
425
- const collection = await this.collection;
426
- const {
427
- result
428
- } = await collection.insertOne(object);
429
-
430
- if (!result.ok || result.n !== 1) {
431
- throw new Error('Fail to insert');
432
- }
433
-
434
- return object;
435
- }
436
-
437
- async replaceOne(object) {
438
- if (!object.updated) object.updated = new Date();
439
- const collection = await this.collection;
440
- await collection.replaceOne({
441
- _id: object._id
442
- }, object);
443
- return object;
444
- }
445
-
446
- async upsertOne(object, setOnInsertPartialObject) {
447
- const result = await this.upsertOneWithInfo(object, setOnInsertPartialObject);
448
- return result.object;
449
- }
450
-
451
- async upsertOneWithInfo(object, setOnInsertPartialObject) {
452
- const $setOnInsert = {
453
- created: object.created || new Date(),
454
- ...setOnInsertPartialObject
455
- };
456
- if (!object.updated) object.updated = new Date();
457
- const $set = { ...object
458
- };
459
- delete $set.created;
460
- const collection = await this.collection;
461
- const {
462
- upsertedCount
463
- } = await collection.updateOne({
464
- _id: object._id
465
- }, {
466
- $set,
467
- $setOnInsert
468
- }, {
469
- upsert: true
470
- });
471
-
472
- if (upsertedCount) {
473
- Object.assign(object, $setOnInsert);
474
- }
475
-
476
- return {
477
- object: object,
478
- inserted: !!upsertedCount
479
- };
480
- }
481
-
482
- replaceSeveral(objects) {
483
- return Promise.all(objects.map(object => this.replaceOne(object)));
484
- }
485
-
486
- async partialUpdateByKey(key, partialUpdate, criteria) {
487
- const collection = await this.collection;
488
- const commandResult = await collection.updateOne({
489
- _id: key,
490
- ...criteria
491
- }, partialUpdate);
492
-
493
- if (!commandResult.result.ok) {
494
- console.error(commandResult);
495
- throw new Error('Update failed');
496
- }
497
-
498
- const object = await this.findByKey(key);
499
- return object;
500
- }
501
-
502
- partialUpdateOne(object, partialUpdate) {
503
- return this.partialUpdateByKey(object._id, partialUpdate);
504
- }
505
-
506
- partialUpdateMany(criteria, partialUpdate) {
507
- return this.collection.then(collection => collection.updateMany(criteria, partialUpdate)).then(() => undefined); // TODO return updated object
508
- }
509
-
510
- deleteByKey(key, criteria) {
511
- return this.collection.then(collection => collection.deleteOne({
512
- _id: key,
513
- ...criteria
514
- })).then(() => undefined);
515
- }
516
-
517
- deleteOne(object) {
518
- return this.deleteByKey(object._id);
519
- }
520
-
521
- deleteMany(selector) {
522
- return this.collection.then(collection => collection.deleteMany(selector)).then(() => undefined);
523
- }
524
-
525
- cursor(criteria, sort) {
526
- return this.collection.then(collection => collection.find(criteria)).then(sort && (cursor => cursor.sort(sort))).then(cursor => new MongoCursor(this, cursor));
527
- }
528
-
529
- findByKey(key, criteria) {
530
- return this.collection.then(collection => collection.findOne({
531
- _id: key,
532
- ...criteria
533
- })).then(result => result || undefined);
534
- }
535
-
536
- findAll(criteria, sort) {
537
- return this.cursor(criteria, sort).then(cursor => cursor.toArray());
538
- }
539
-
540
- findOne(criteria, sort) {
541
- return this.collection.then(collection => collection.find(criteria)).then(sort && (cursor => cursor.sort(sort))).then(cursor => cursor.limit(1).next());
542
- }
543
-
544
- }
545
-
546
- const logger = new Logger('liwi:mongo:MongoConnection');
547
- class MongoConnection extends AbstractConnection {
548
- constructor(config) {
549
- super();
550
-
551
- if (!config.has('host')) {
552
- config.set('host', 'localhost');
553
- }
554
-
555
- if (!config.has('port')) {
556
- config.set('port', '27017');
557
- }
558
-
559
- if (!config.has('database')) {
560
- throw new Error('Missing config database');
561
- }
562
-
563
- const connectionString = `mongodb://${config.has('user') ? `${config.get('user')}:${config.get('password')}@` : ''}` + `${config.get('host')}:${config.get('port')}/${config.get('database')}`;
564
- this.connect(connectionString);
565
- }
566
-
567
- connect(connectionString) {
568
- logger.info('connecting', {
569
- connectionString
570
- });
571
- const connectPromise = MongoClient.connect(connectionString, {
572
- useNewUrlParser: true
573
- }).then(connection => {
574
- logger.info('connected', {
575
- connectionString
576
- });
577
- connection.on('close', () => {
578
- logger.warn('close', {
579
- connectionString
580
- });
581
- this.connectionFailed = true;
582
-
583
- this.getConnection = () => {
584
- throw new Error('MongoDB connection closed');
585
- };
586
- });
587
- connection.on('timeout', () => {
588
- logger.warn('timeout', {
589
- connectionString
590
- });
591
- this.connectionFailed = true;
592
-
593
- this.getConnection = () => {
594
- throw new Error('MongoDB connection timeout');
595
- };
596
- });
597
- connection.on('reconnect', () => {
598
- logger.warn('reconnect', {
599
- connectionString
600
- });
601
- this.connectionFailed = false;
602
-
603
- this.getConnection = () => Promise.resolve(this._connection);
604
- });
605
- connection.on('error', err => {
606
- logger.warn('error', {
607
- connectionString,
608
- err
609
- });
610
- });
611
- this._connection = connection;
612
- this._connecting = undefined;
613
-
614
- this.getConnection = () => Promise.resolve(this._connection);
615
-
616
- return connection;
617
- }).catch(err => {
618
- logger.info('not connected', {
619
- connectionString
620
- });
621
- console.error(err.message || err); // throw err;
622
-
623
- process.nextTick(() => {
624
- // eslint-disable-next-line unicorn/no-process-exit
625
- process.exit(1);
626
- });
627
- throw err;
628
- });
629
-
630
- this.getConnection = () => Promise.resolve(connectPromise);
631
-
632
- this._connecting = this.getConnection();
633
- }
634
-
635
- getConnection() {
636
- throw new Error('call connect()');
637
- }
638
-
639
- async close() {
640
- this.getConnection = () => Promise.reject(new Error('Connection closed'));
641
-
642
- if (this._connection) {
643
- await this._connection.close();
644
- this._connection = undefined;
645
- } else if (this._connecting) {
646
- await this._connecting;
647
- await this.close();
648
- }
649
- }
650
-
651
- }
652
-
653
- function createMongoSubscribeStore(mongoStore) {
654
- return new SubscribeStore(mongoStore);
655
- }
656
-
657
- export { MongoConnection, MongoStore, createMongoSubscribeStore };
658
- //# sourceMappingURL=index-node12.es.js.map