nx-mongo 4.0.0 â 4.0.1
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/README.md +1743 -1726
- package/dist/simpleMongoHelper.js +12 -18
- package/dist/simpleMongoHelper.js.map +1 -1
- package/dist/test.js +3 -5
- package/dist/test.js.map +1 -1
- package/package.json +46 -42
- package/renovate.json +17 -0
- package/src/simpleMongoHelper.ts +2638 -2638
- package/src/test.ts +3 -3
- package/tsconfig.json +2 -2
package/README.md
CHANGED
|
@@ -1,1726 +1,1743 @@
|
|
|
1
|
-
# nx-mongo
|
|
2
|
-
|
|
3
|
-
**Version:** 4.0.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
This
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- â
**
|
|
18
|
-
- â
**
|
|
19
|
-
- â
**
|
|
20
|
-
- â
**
|
|
21
|
-
- â
**
|
|
22
|
-
- â
**
|
|
23
|
-
- â
**
|
|
24
|
-
- â
**
|
|
25
|
-
- â
**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
'
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- `config.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
- `
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
case '
|
|
244
|
-
console.error('
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
- `
|
|
264
|
-
- `
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
-
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
- `
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
**
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// result.
|
|
335
|
-
// result.
|
|
336
|
-
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
- `
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
- `
|
|
470
|
-
- `
|
|
471
|
-
- `
|
|
472
|
-
- `
|
|
473
|
-
- `
|
|
474
|
-
- `
|
|
475
|
-
- `
|
|
476
|
-
- `
|
|
477
|
-
- `
|
|
478
|
-
- `
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
- **
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
-
|
|
652
|
-
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
**
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
###
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
{ ref: "
|
|
831
|
-
{ ref: "
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
// Priority
|
|
895
|
-
await helper.insert('users', { name: 'John' }, {}, undefined, 'app1');
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
// Priority
|
|
899
|
-
await helper.insert('users', { name: 'John' }, {}, undefined,
|
|
900
|
-
// Throws error if multiple matches found
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
**
|
|
912
|
-
|
|
913
|
-
- If
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
// Use ref
|
|
935
|
-
|
|
936
|
-
//
|
|
937
|
-
|
|
938
|
-
// Use
|
|
939
|
-
// This would throw error because
|
|
940
|
-
// await helper.insert('users', { name: 'John' }, {}, undefined,
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
- `
|
|
957
|
-
- `
|
|
958
|
-
- `
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
//
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
- `
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
```
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
// Get all completed stages
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
```
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
```
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
//
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
**
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
- **
|
|
1341
|
-
- **
|
|
1342
|
-
- **
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
{ ref: "
|
|
1363
|
-
{ ref: "
|
|
1364
|
-
],
|
|
1365
|
-
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
//
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
//
|
|
1380
|
-
await helper.writeByRef('
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
```
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
```
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
```
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
```
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
```
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
```
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
```
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
```
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
```
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
```
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
**
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
##
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
##
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
-
|
|
1642
|
-
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
-
|
|
1646
|
-
-
|
|
1647
|
-
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
- **
|
|
1652
|
-
-
|
|
1653
|
-
-
|
|
1654
|
-
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
- **
|
|
1659
|
-
-
|
|
1660
|
-
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
-
|
|
1664
|
-
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
-
|
|
1671
|
-
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
-
|
|
1675
|
-
-
|
|
1676
|
-
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
-
|
|
1681
|
-
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
-
|
|
1688
|
-
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
-
|
|
1692
|
-
-
|
|
1693
|
-
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
- Added
|
|
1702
|
-
-
|
|
1703
|
-
-
|
|
1704
|
-
-
|
|
1705
|
-
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
-
|
|
1711
|
-
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
- Added
|
|
1716
|
-
- Added
|
|
1717
|
-
- Added
|
|
1718
|
-
- Added
|
|
1719
|
-
- Added
|
|
1720
|
-
- Added
|
|
1721
|
-
- Added
|
|
1722
|
-
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1
|
+
# nx-mongo
|
|
2
|
+
|
|
3
|
+
**Version:** 4.0.1
|
|
4
|
+
|
|
5
|
+
## đĻ ES Module Package
|
|
6
|
+
|
|
7
|
+
This package is now an **ES module** package (`"type": "module"`). It uses ES module syntax (`import`/`export`) and is compatible with ES module environments.
|
|
8
|
+
|
|
9
|
+
## đ Env-Ready Component (ERC 2.0)
|
|
10
|
+
|
|
11
|
+
This component supports **zero-config initialization** via environment variables using [nx-config2](https://github.com/xeonox/nx-config2).
|
|
12
|
+
|
|
13
|
+
A lightweight, feature-rich MongoDB helper library for Node.js and TypeScript. Provides a simple, intuitive API for common MongoDB operations with built-in retry logic, pagination, transactions, config-driven ref mapping, and signature-based deduplication.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- â
**Simple API** - Easy-to-use methods for common MongoDB operations
|
|
18
|
+
- â
**TypeScript Support** - Full TypeScript support with type safety
|
|
19
|
+
- â
**Connection Retry** - Automatic retry with exponential backoff
|
|
20
|
+
- â
**Automatic Cleanup** - Connections automatically close on app exit (SIGINT, SIGTERM, etc.)
|
|
21
|
+
- â
**Pagination** - Built-in pagination support with metadata
|
|
22
|
+
- â
**Transactions** - Full transaction support for multi-operation consistency
|
|
23
|
+
- â
**Aggregation** - Complete aggregation pipeline support
|
|
24
|
+
- â
**Index Management** - Create, drop, and list indexes
|
|
25
|
+
- â
**Count Operations** - Accurate and estimated document counting
|
|
26
|
+
- â
**Session Support** - Transaction sessions for complex operations
|
|
27
|
+
- â
**Config-driven Ref Mapping** - Map application-level refs to MongoDB collections
|
|
28
|
+
- â
**Signature-based Deduplication** - Automatic duplicate prevention using document signatures
|
|
29
|
+
- â
**Append/Replace Modes** - Flexible write modes for data pipelines
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install nx-mongo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### ERC 2.0 Setup
|
|
38
|
+
|
|
39
|
+
1. Copy `.env.example` to `.env`:
|
|
40
|
+
```bash
|
|
41
|
+
cp node_modules/nx-mongo/.env.example .env
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
2. Fill in required values in `.env`:
|
|
45
|
+
```env
|
|
46
|
+
MONGO_CONNECTION_STRING=mongodb://localhost:27017/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. Use with zero config:
|
|
50
|
+
```typescript
|
|
51
|
+
const helper = new SimpleMongoHelper();
|
|
52
|
+
await helper.initialize();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Zero-Config Mode (ERC 2.0)
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { SimpleMongoHelper } from 'nx-mongo';
|
|
61
|
+
|
|
62
|
+
// Auto-discovers configuration from environment variables
|
|
63
|
+
// Set MONGO_CONNECTION_STRING or MONGODB_URI in your .env file
|
|
64
|
+
const helper = new SimpleMongoHelper();
|
|
65
|
+
|
|
66
|
+
// Initialize connection
|
|
67
|
+
await helper.initialize();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Environment Variables:**
|
|
71
|
+
- `MONGO_CONNECTION_STRING` or `MONGODB_URI` (required) - MongoDB connection string
|
|
72
|
+
- `MONGO_MAX_RETRIES` (optional, default: 3) - Maximum retry attempts
|
|
73
|
+
- `MONGO_RETRY_DELAY` (optional, default: 1000) - Initial retry delay in milliseconds
|
|
74
|
+
- `MONGO_EXPONENTIAL_BACKOFF` (optional, default: true) - Use exponential backoff
|
|
75
|
+
|
|
76
|
+
See `.env.example` for the complete list of required and optional variables with descriptions.
|
|
77
|
+
|
|
78
|
+
### Advanced Mode (Programmatic Configuration)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { SimpleMongoHelper } from 'nx-mongo';
|
|
82
|
+
|
|
83
|
+
// Explicit configuration (bypasses auto-discovery)
|
|
84
|
+
const helper = new SimpleMongoHelper('mongodb://localhost:27017/', {
|
|
85
|
+
maxRetries: 5,
|
|
86
|
+
retryDelay: 2000
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Or use config object
|
|
90
|
+
const helper = new SimpleMongoHelper({
|
|
91
|
+
connectionString: 'mongodb://localhost:27017/',
|
|
92
|
+
retryOptions: {
|
|
93
|
+
maxRetries: 5,
|
|
94
|
+
retryDelay: 2000
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Initialize connection
|
|
99
|
+
await helper.initialize();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Legacy Mode (Backward Compatible)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { SimpleMongoHelper } from 'nx-mongo';
|
|
106
|
+
|
|
107
|
+
// Connection string: database name is ignored/stripped automatically
|
|
108
|
+
// Use base connection string: mongodb://localhost:27017/
|
|
109
|
+
const helper = new SimpleMongoHelper('mongodb://localhost:27017/');
|
|
110
|
+
|
|
111
|
+
// Initialize connection
|
|
112
|
+
await helper.initialize();
|
|
113
|
+
|
|
114
|
+
// Insert a document (defaults to 'admin' database)
|
|
115
|
+
await helper.insert('users', {
|
|
116
|
+
name: 'John Doe',
|
|
117
|
+
email: 'john@example.com',
|
|
118
|
+
age: 30
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Insert into a specific database
|
|
122
|
+
await helper.insert('users', {
|
|
123
|
+
name: 'Jane Doe',
|
|
124
|
+
email: 'jane@example.com',
|
|
125
|
+
age: 28
|
|
126
|
+
}, {}, 'mydb'); // Specify database name
|
|
127
|
+
|
|
128
|
+
// Find documents from 'admin' database (default)
|
|
129
|
+
const users = await helper.loadCollection('users');
|
|
130
|
+
|
|
131
|
+
// Find documents from specific database
|
|
132
|
+
const mydbUsers = await helper.loadCollection('users', {}, undefined, 'mydb');
|
|
133
|
+
|
|
134
|
+
// Find one document
|
|
135
|
+
const user = await helper.findOne('users', { email: 'john@example.com' });
|
|
136
|
+
|
|
137
|
+
// Find from specific database
|
|
138
|
+
const mydbUser = await helper.findOne('users', { email: 'jane@example.com' }, undefined, 'mydb');
|
|
139
|
+
|
|
140
|
+
// Update document
|
|
141
|
+
await helper.update(
|
|
142
|
+
'users',
|
|
143
|
+
{ email: 'john@example.com' },
|
|
144
|
+
{ $set: { age: 31 } }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Update in specific database
|
|
148
|
+
await helper.update(
|
|
149
|
+
'users',
|
|
150
|
+
{ email: 'jane@example.com' },
|
|
151
|
+
{ $set: { age: 29 } },
|
|
152
|
+
undefined,
|
|
153
|
+
'mydb'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Delete document
|
|
157
|
+
await helper.delete('users', { email: 'john@example.com' });
|
|
158
|
+
|
|
159
|
+
// Disconnect
|
|
160
|
+
await helper.disconnect();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Note:** The connection string database name (if present) is automatically stripped. All operations default to the `'admin'` database unless you specify a different database name as the last parameter.
|
|
164
|
+
|
|
165
|
+
## API Reference
|
|
166
|
+
|
|
167
|
+
### Constructor
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Zero-Config Mode (ERC 2.0)
|
|
171
|
+
new SimpleMongoHelper()
|
|
172
|
+
|
|
173
|
+
// Advanced Mode (Config Object)
|
|
174
|
+
new SimpleMongoHelper(config: SimpleMongoHelperConfig)
|
|
175
|
+
|
|
176
|
+
// Legacy Mode (Backward Compatible)
|
|
177
|
+
new SimpleMongoHelper(connectionString: string, retryOptions?: RetryOptions, config?: HelperConfig)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
|
|
182
|
+
**Zero-Config Mode:**
|
|
183
|
+
- No parameters - auto-discovers from environment variables
|
|
184
|
+
|
|
185
|
+
**Advanced Mode (Config Object):**
|
|
186
|
+
- `config.connectionString` (optional) - MongoDB connection string (defaults to `MONGO_CONNECTION_STRING` or `MONGODB_URI` env var)
|
|
187
|
+
- `config.retryOptions` (optional) - Retry configuration
|
|
188
|
+
- `maxRetries?: number` - Maximum retry attempts (default: 3, or `MONGO_MAX_RETRIES` env var)
|
|
189
|
+
- `retryDelay?: number` - Initial retry delay in ms (default: 1000, or `MONGO_RETRY_DELAY` env var)
|
|
190
|
+
- `exponentialBackoff?: boolean` - Use exponential backoff (default: true, or `MONGO_EXPONENTIAL_BACKOFF` env var)
|
|
191
|
+
- `config.config` (optional) - HelperConfig for ref-based operations
|
|
192
|
+
|
|
193
|
+
**Legacy Mode:**
|
|
194
|
+
- `connectionString` - MongoDB base connection string (database name is automatically stripped if present)
|
|
195
|
+
- Example: `'mongodb://localhost:27017/'` or `'mongodb://localhost:27017/admin'` (both work the same)
|
|
196
|
+
- `retryOptions` (optional) - Retry configuration
|
|
197
|
+
- `config` (optional) - HelperConfig for ref-based operations
|
|
198
|
+
|
|
199
|
+
**Examples:**
|
|
200
|
+
```typescript
|
|
201
|
+
// Zero-Config Mode (ERC 2.0)
|
|
202
|
+
const helper = new SimpleMongoHelper(); // Uses MONGO_CONNECTION_STRING from env
|
|
203
|
+
|
|
204
|
+
// Advanced Mode
|
|
205
|
+
const helper = new SimpleMongoHelper({
|
|
206
|
+
connectionString: 'mongodb://localhost:27017/',
|
|
207
|
+
retryOptions: { maxRetries: 5, retryDelay: 2000 }
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Legacy Mode (still supported)
|
|
211
|
+
const helper = new SimpleMongoHelper(
|
|
212
|
+
'mongodb://localhost:27017/',
|
|
213
|
+
{ maxRetries: 5, retryDelay: 2000 }
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Important:** The database name in the connection string is automatically stripped. All operations default to the `'admin'` database unless you specify a different database name per operation.
|
|
218
|
+
|
|
219
|
+
### Connection Methods
|
|
220
|
+
|
|
221
|
+
#### `testConnection(): Promise<{ success: boolean; error?: { type: string; message: string; details?: string } }>`
|
|
222
|
+
|
|
223
|
+
Tests the MongoDB connection and returns detailed error information if it fails. This method does not establish a persistent connection - use `initialize()` for that.
|
|
224
|
+
|
|
225
|
+
**Returns:**
|
|
226
|
+
- `success: boolean` - Whether the connection test succeeded
|
|
227
|
+
- `error?: object` - Error details if connection failed
|
|
228
|
+
- `type` - Error type: `'missing_credentials' | 'invalid_connection_string' | 'connection_failed' | 'authentication_failed' | 'config_error' | 'unknown'`
|
|
229
|
+
- `message` - Human-readable error message
|
|
230
|
+
- `details` - Detailed error information and troubleshooting tips
|
|
231
|
+
|
|
232
|
+
**Example:**
|
|
233
|
+
```typescript
|
|
234
|
+
const result = await helper.testConnection();
|
|
235
|
+
if (!result.success) {
|
|
236
|
+
console.error('Connection test failed!');
|
|
237
|
+
console.error('Error Type:', result.error?.type);
|
|
238
|
+
console.error('Error Message:', result.error?.message);
|
|
239
|
+
console.error('Error Details:', result.error?.details);
|
|
240
|
+
|
|
241
|
+
// Handle error based on type
|
|
242
|
+
switch (result.error?.type) {
|
|
243
|
+
case 'connection_failed':
|
|
244
|
+
console.error('Cannot connect to MongoDB server. Check if server is running.');
|
|
245
|
+
// On Windows, try using 127.0.0.1 instead of localhost
|
|
246
|
+
break;
|
|
247
|
+
case 'authentication_failed':
|
|
248
|
+
console.error('Invalid credentials. Check username and password.');
|
|
249
|
+
break;
|
|
250
|
+
case 'invalid_connection_string':
|
|
251
|
+
console.error('Connection string format is invalid.');
|
|
252
|
+
break;
|
|
253
|
+
default:
|
|
254
|
+
console.error('Unknown error occurred.');
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
console.log('Connection test passed!');
|
|
258
|
+
await helper.initialize();
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Error Types:**
|
|
263
|
+
- `missing_credentials` - Username or password missing in connection string
|
|
264
|
+
- `invalid_connection_string` - Connection string format is invalid
|
|
265
|
+
- `connection_failed` - Cannot reach MongoDB server (timeout, DNS, network, etc.)
|
|
266
|
+
- `authentication_failed` - Invalid credentials or insufficient permissions
|
|
267
|
+
- `config_error` - Configuration issues
|
|
268
|
+
- `unknown` - Unexpected error
|
|
269
|
+
|
|
270
|
+
**Troubleshooting Tips:**
|
|
271
|
+
- **Windows users**: If using `localhost` fails, try `127.0.0.1` instead (e.g., `mongodb://127.0.0.1:27017/`) to avoid IPv6 resolution issues
|
|
272
|
+
- **Connection timeout**: Verify MongoDB is running and accessible on the specified host and port
|
|
273
|
+
- **Connection refused**: Check if MongoDB is listening on the correct port (default: 27017)
|
|
274
|
+
- **Authentication failed**: Verify username and password in the connection string
|
|
275
|
+
|
|
276
|
+
#### `initialize(): Promise<void>`
|
|
277
|
+
|
|
278
|
+
Establishes MongoDB connection with automatic retry logic. Must be called before using other methods.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
await helper.initialize();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `disconnect(): Promise<void>`
|
|
285
|
+
|
|
286
|
+
Closes the MongoDB connection and cleans up resources. **Note:** Connections are automatically closed when your application exits (handles SIGINT, SIGTERM, and beforeExit events), so manual disconnection is optional but recommended for explicit cleanup.
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
await helper.disconnect();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Automatic Cleanup:**
|
|
293
|
+
- Connections are automatically closed when the Node.js process receives `SIGINT` (Ctrl+C) or `SIGTERM` signals
|
|
294
|
+
- All `SimpleMongoHelper` instances are cleaned up in parallel with a 5-second timeout
|
|
295
|
+
- Multiple instances are handled gracefully through a global registry
|
|
296
|
+
- Manual `disconnect()` is still recommended for explicit cleanup in your code
|
|
297
|
+
|
|
298
|
+
### Query Methods
|
|
299
|
+
|
|
300
|
+
#### `loadCollection<T>(collectionName: string, query?: Filter<T>, options?: PaginationOptions, database?: string): Promise<WithId<T>[] | PaginatedResult<T>>`
|
|
301
|
+
|
|
302
|
+
Loads documents from a collection with optional query filter and pagination.
|
|
303
|
+
|
|
304
|
+
**Parameters:**
|
|
305
|
+
- `collectionName` - Name of the collection
|
|
306
|
+
- `query` (optional) - MongoDB query filter
|
|
307
|
+
- `options` (optional) - Pagination and sorting options
|
|
308
|
+
- `page?: number` - Page number (1-indexed)
|
|
309
|
+
- `limit?: number` - Documents per page
|
|
310
|
+
- `sort?: Sort` - Sort specification
|
|
311
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
312
|
+
|
|
313
|
+
**Returns:**
|
|
314
|
+
- Without pagination: `WithId<T>[]`
|
|
315
|
+
- With pagination: `PaginatedResult<T>` with metadata
|
|
316
|
+
|
|
317
|
+
**Examples:**
|
|
318
|
+
```typescript
|
|
319
|
+
// Load all documents from 'admin' database (default)
|
|
320
|
+
const allUsers = await helper.loadCollection('users');
|
|
321
|
+
|
|
322
|
+
// Load from specific database
|
|
323
|
+
const mydbUsers = await helper.loadCollection('users', {}, undefined, 'mydb');
|
|
324
|
+
|
|
325
|
+
// Load with query
|
|
326
|
+
const activeUsers = await helper.loadCollection('users', { active: true });
|
|
327
|
+
|
|
328
|
+
// Load with pagination
|
|
329
|
+
const result = await helper.loadCollection('users', {}, {
|
|
330
|
+
page: 1,
|
|
331
|
+
limit: 10,
|
|
332
|
+
sort: { createdAt: -1 }
|
|
333
|
+
});
|
|
334
|
+
// result.data - array of documents
|
|
335
|
+
// result.total - total count
|
|
336
|
+
// result.page - current page
|
|
337
|
+
// result.totalPages - total pages
|
|
338
|
+
// result.hasNext - has next page
|
|
339
|
+
// result.hasPrev - has previous page
|
|
340
|
+
|
|
341
|
+
// Load with pagination from specific database
|
|
342
|
+
const mydbResult = await helper.loadCollection('users', {}, {
|
|
343
|
+
page: 1,
|
|
344
|
+
limit: 10,
|
|
345
|
+
sort: { createdAt: -1 }
|
|
346
|
+
}, 'mydb');
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### `findOne<T>(collectionName: string, query: Filter<T>, options?: { sort?: Sort; projection?: Document }, database?: string): Promise<WithId<T> | null>`
|
|
350
|
+
|
|
351
|
+
Finds a single document in a collection.
|
|
352
|
+
|
|
353
|
+
**Parameters:**
|
|
354
|
+
- `collectionName` - Name of the collection
|
|
355
|
+
- `query` - MongoDB query filter
|
|
356
|
+
- `options` (optional) - Find options
|
|
357
|
+
- `sort?: Sort` - Sort specification
|
|
358
|
+
- `projection?: Document` - Field projection
|
|
359
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
360
|
+
|
|
361
|
+
**Example:**
|
|
362
|
+
```typescript
|
|
363
|
+
const user = await helper.findOne('users', { email: 'john@example.com' });
|
|
364
|
+
const latestUser = await helper.findOne('users', {}, { sort: { createdAt: -1 } });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Insert Methods
|
|
368
|
+
|
|
369
|
+
#### `insert<T>(collectionName: string, data: T | T[], options?: { session?: ClientSession }, database?: string): Promise<any>`
|
|
370
|
+
|
|
371
|
+
Inserts one or more documents into a collection.
|
|
372
|
+
|
|
373
|
+
**Parameters:**
|
|
374
|
+
- `collectionName` - Name of the collection
|
|
375
|
+
- `data` - Single document or array of documents
|
|
376
|
+
- `options` (optional) - Insert options
|
|
377
|
+
- `session?: ClientSession` - Transaction session
|
|
378
|
+
|
|
379
|
+
**Examples:**
|
|
380
|
+
```typescript
|
|
381
|
+
// Insert single document
|
|
382
|
+
await helper.insert('users', {
|
|
383
|
+
name: 'John Doe',
|
|
384
|
+
email: 'john@example.com'
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Insert multiple documents
|
|
388
|
+
await helper.insert('users', [
|
|
389
|
+
{ name: 'John', email: 'john@example.com' },
|
|
390
|
+
{ name: 'Jane', email: 'jane@example.com' }
|
|
391
|
+
]);
|
|
392
|
+
|
|
393
|
+
// Insert within transaction
|
|
394
|
+
const session = helper.startSession();
|
|
395
|
+
await session.withTransaction(async () => {
|
|
396
|
+
await helper.insert('users', { name: 'John' }, { session });
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Update Methods
|
|
401
|
+
|
|
402
|
+
#### `update<T>(collectionName: string, filter: Filter<T>, updateData: UpdateFilter<T>, options?: { upsert?: boolean; multi?: boolean; session?: ClientSession }, database?: string): Promise<any>`
|
|
403
|
+
|
|
404
|
+
Updates documents in a collection.
|
|
405
|
+
|
|
406
|
+
**Parameters:**
|
|
407
|
+
- `collectionName` - Name of the collection
|
|
408
|
+
- `filter` - MongoDB query filter
|
|
409
|
+
- `updateData` - Update operations
|
|
410
|
+
- `options` (optional) - Update options
|
|
411
|
+
- `upsert?: boolean` - Create if not exists
|
|
412
|
+
- `multi?: boolean` - Update multiple documents (default: false)
|
|
413
|
+
- `session?: ClientSession` - Transaction session
|
|
414
|
+
|
|
415
|
+
**Examples:**
|
|
416
|
+
```typescript
|
|
417
|
+
// Update single document
|
|
418
|
+
await helper.update(
|
|
419
|
+
'users',
|
|
420
|
+
{ email: 'john@example.com' },
|
|
421
|
+
{ $set: { age: 31 } }
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Update multiple documents
|
|
425
|
+
await helper.update(
|
|
426
|
+
'users',
|
|
427
|
+
{ role: 'user' },
|
|
428
|
+
{ $set: { lastLogin: new Date() } },
|
|
429
|
+
{ multi: true }
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Upsert (create if not exists)
|
|
433
|
+
await helper.update(
|
|
434
|
+
'users',
|
|
435
|
+
{ email: 'john@example.com' },
|
|
436
|
+
{ $set: { name: 'John Doe', email: 'john@example.com' } },
|
|
437
|
+
{ upsert: true }
|
|
438
|
+
);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Delete Methods
|
|
442
|
+
|
|
443
|
+
#### `delete<T>(collectionName: string, filter: Filter<T>, options?: { multi?: boolean }, database?: string): Promise<any>`
|
|
444
|
+
|
|
445
|
+
Deletes documents from a collection.
|
|
446
|
+
|
|
447
|
+
**Parameters:**
|
|
448
|
+
- `collectionName` - Name of the collection
|
|
449
|
+
- `filter` - MongoDB query filter
|
|
450
|
+
- `options` (optional) - Delete options
|
|
451
|
+
- `multi?: boolean` - Delete multiple documents (default: false)
|
|
452
|
+
|
|
453
|
+
**Examples:**
|
|
454
|
+
```typescript
|
|
455
|
+
// Delete single document
|
|
456
|
+
await helper.delete('users', { email: 'john@example.com' });
|
|
457
|
+
|
|
458
|
+
// Delete multiple documents
|
|
459
|
+
await helper.delete('users', { role: 'guest' }, { multi: true });
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Collection Merge Methods
|
|
463
|
+
|
|
464
|
+
#### `mergeCollections(options: MergeCollectionsOptions): Promise<MergeCollectionsResult>`
|
|
465
|
+
|
|
466
|
+
Merges two collections into a new target collection using various strategies (index-based, key-based, or composite-key). Useful for combining original records with assessment results or joining related data.
|
|
467
|
+
|
|
468
|
+
**Parameters:**
|
|
469
|
+
- `sourceCollection1` - Name of first source collection (e.g., original records)
|
|
470
|
+
- `sourceCollection2` - Name of second source collection (e.g., assessment results)
|
|
471
|
+
- `targetCollection` - Name of target collection for merged results
|
|
472
|
+
- `strategy` - Merge strategy: `'index' | 'key' | 'composite'`
|
|
473
|
+
- `key` - (For 'key' strategy) Field name to match on (supports dot notation)
|
|
474
|
+
- `compositeKeys` - (For 'composite' strategy) Array of field names for composite key matching
|
|
475
|
+
- `joinType` - (For 'key' and 'composite' strategies) SQL-style join type: `'inner' | 'left' | 'right' | 'outer'` (optional, overrides onUnmatched flags)
|
|
476
|
+
- `fieldPrefix1` - Prefix for fields from collection 1 (default: 'record')
|
|
477
|
+
- `fieldPrefix2` - Prefix for fields from collection 2 (default: 'assessment')
|
|
478
|
+
- `includeIndex` - Include original index in merged document (default: true for index strategy)
|
|
479
|
+
- `onUnmatched1` - (Deprecated: use `joinType` instead) What to do with unmatched records from collection 1: 'include' | 'skip' (default: 'include')
|
|
480
|
+
- `onUnmatched2` - (Deprecated: use `joinType` instead) What to do with unmatched records from collection 2: 'include' | 'skip' (default: 'include')
|
|
481
|
+
- `session` - Optional transaction session
|
|
482
|
+
- `database` - Optional database name (defaults to `'admin'`)
|
|
483
|
+
|
|
484
|
+
**Returns:**
|
|
485
|
+
```typescript
|
|
486
|
+
interface MergeCollectionsResult {
|
|
487
|
+
merged: number; // Total merged documents
|
|
488
|
+
unmatched1: number; // Unmatched documents from collection 1
|
|
489
|
+
unmatched2: number; // Unmatched documents from collection 2
|
|
490
|
+
errors: Array<{ index: number; error: Error; doc?: any }>;
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Strategies:**
|
|
495
|
+
|
|
496
|
+
1. **Index-based** (`strategy: 'index'`): Merges by array position. Assumes both collections are in the same order.
|
|
497
|
+
```typescript
|
|
498
|
+
const result = await helper.mergeCollections({
|
|
499
|
+
sourceCollection1: 'original_records',
|
|
500
|
+
sourceCollection2: 'assessments',
|
|
501
|
+
targetCollection: 'merged_results',
|
|
502
|
+
strategy: 'index',
|
|
503
|
+
fieldPrefix1: 'record',
|
|
504
|
+
fieldPrefix2: 'assessment',
|
|
505
|
+
includeIndex: true
|
|
506
|
+
});
|
|
507
|
+
// Result: { recordIndex: 0, record: {...}, assessment: {...} }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
2. **Key-based** (`strategy: 'key'`): Merges by matching a single unique field. Supports SQL-style join types.
|
|
511
|
+
```typescript
|
|
512
|
+
// INNER JOIN - Only matched records
|
|
513
|
+
const result = await helper.mergeCollections({
|
|
514
|
+
sourceCollection1: 'applications',
|
|
515
|
+
sourceCollection2: 'assessments',
|
|
516
|
+
targetCollection: 'merged',
|
|
517
|
+
strategy: 'key',
|
|
518
|
+
key: 'id',
|
|
519
|
+
joinType: 'inner' // Only records with matching assessments
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// LEFT JOIN - All records, with assessments where available
|
|
523
|
+
const result = await helper.mergeCollections({
|
|
524
|
+
sourceCollection1: 'applications',
|
|
525
|
+
sourceCollection2: 'assessments',
|
|
526
|
+
targetCollection: 'merged',
|
|
527
|
+
strategy: 'key',
|
|
528
|
+
key: 'id',
|
|
529
|
+
joinType: 'left' // All apps, null assessment if no match
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// RIGHT JOIN - All assessments, with records where available
|
|
533
|
+
const result = await helper.mergeCollections({
|
|
534
|
+
sourceCollection1: 'applications',
|
|
535
|
+
sourceCollection2: 'assessments',
|
|
536
|
+
targetCollection: 'merged',
|
|
537
|
+
strategy: 'key',
|
|
538
|
+
key: 'id',
|
|
539
|
+
joinType: 'right' // All assessments, null record if no match
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// FULL OUTER JOIN - Everything from both sides
|
|
543
|
+
const result = await helper.mergeCollections({
|
|
544
|
+
sourceCollection1: 'applications',
|
|
545
|
+
sourceCollection2: 'assessments',
|
|
546
|
+
targetCollection: 'merged',
|
|
547
|
+
strategy: 'key',
|
|
548
|
+
key: 'id',
|
|
549
|
+
joinType: 'outer' // All apps and all assessments
|
|
550
|
+
});
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
3. **Composite-key** (`strategy: 'composite'`): Merges by matching multiple fields (e.g., name + ports + zones). Also supports join types.
|
|
554
|
+
```typescript
|
|
555
|
+
const result = await helper.mergeCollections({
|
|
556
|
+
sourceCollection1: 'original_records',
|
|
557
|
+
sourceCollection2: 'assessments',
|
|
558
|
+
targetCollection: 'merged',
|
|
559
|
+
strategy: 'composite',
|
|
560
|
+
compositeKeys: ['name', 'ports[]', 'zones[]'], // Arrays are sorted for matching
|
|
561
|
+
joinType: 'left', // All records, assessments where match
|
|
562
|
+
fieldPrefix1: 'record',
|
|
563
|
+
fieldPrefix2: 'assessment'
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**SQL-Style Join Types:**
|
|
568
|
+
|
|
569
|
+
- **`'inner'`** - INNER JOIN: Returns only records that have matches in both collections
|
|
570
|
+
- **`'left'`** - LEFT JOIN: Returns all records from collection 1, with matching records from collection 2 (null if no match)
|
|
571
|
+
- **`'right'`** - RIGHT JOIN: Returns all records from collection 2, with matching records from collection 1 (null if no match)
|
|
572
|
+
- **`'outer'`** - FULL OUTER JOIN: Returns all records from both collections, matching where possible
|
|
573
|
+
|
|
574
|
+
**Multiple Matches:** When a key appears multiple times in collection 2, the merge creates multiple rows (one per match), just like SQL joins. For example, if "app1" has 2 assessments, you'll get 2 merged rows.
|
|
575
|
+
|
|
576
|
+
**Examples:**
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// Index-based merge (fast but requires same order)
|
|
580
|
+
const result1 = await helper.mergeCollections({
|
|
581
|
+
sourceCollection1: 'records',
|
|
582
|
+
sourceCollection2: 'assessments',
|
|
583
|
+
targetCollection: 'merged',
|
|
584
|
+
strategy: 'index'
|
|
585
|
+
});
|
|
586
|
+
console.log(`Merged ${result1.merged} documents, ${result1.unmatched1} unmatched from collection 1`);
|
|
587
|
+
|
|
588
|
+
// INNER JOIN - Only complete records (both sides matched)
|
|
589
|
+
const result2 = await helper.mergeCollections({
|
|
590
|
+
sourceCollection1: 'apps',
|
|
591
|
+
sourceCollection2: 'assessments',
|
|
592
|
+
targetCollection: 'merged',
|
|
593
|
+
strategy: 'key',
|
|
594
|
+
key: 'appId',
|
|
595
|
+
joinType: 'inner' // Only apps that have assessments
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// LEFT JOIN - All apps, assessments where available
|
|
599
|
+
const result3 = await helper.mergeCollections({
|
|
600
|
+
sourceCollection1: 'apps',
|
|
601
|
+
sourceCollection2: 'assessments',
|
|
602
|
+
targetCollection: 'merged',
|
|
603
|
+
strategy: 'key',
|
|
604
|
+
key: 'appId',
|
|
605
|
+
joinType: 'left' // All apps, null assessment if no match
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// RIGHT JOIN - All assessments, apps where available
|
|
609
|
+
const result4 = await helper.mergeCollections({
|
|
610
|
+
sourceCollection1: 'apps',
|
|
611
|
+
sourceCollection2: 'assessments',
|
|
612
|
+
targetCollection: 'merged',
|
|
613
|
+
strategy: 'key',
|
|
614
|
+
key: 'appId',
|
|
615
|
+
joinType: 'right' // All assessments, null app if no match
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// FULL OUTER JOIN - Everything from both sides
|
|
619
|
+
const result5 = await helper.mergeCollections({
|
|
620
|
+
sourceCollection1: 'apps',
|
|
621
|
+
sourceCollection2: 'assessments',
|
|
622
|
+
targetCollection: 'merged',
|
|
623
|
+
strategy: 'key',
|
|
624
|
+
key: 'appId',
|
|
625
|
+
joinType: 'outer' // All apps and all assessments
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Composite-key merge with LEFT JOIN
|
|
629
|
+
const result6 = await helper.mergeCollections({
|
|
630
|
+
sourceCollection1: 'original',
|
|
631
|
+
sourceCollection2: 'assessments',
|
|
632
|
+
targetCollection: 'merged',
|
|
633
|
+
strategy: 'composite',
|
|
634
|
+
compositeKeys: ['name', 'ports[]', 'zones[]'],
|
|
635
|
+
joinType: 'left',
|
|
636
|
+
fieldPrefix1: 'record',
|
|
637
|
+
fieldPrefix2: 'assessment',
|
|
638
|
+
includeIndex: true
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Handling multiple matches (one app, multiple assessments)
|
|
642
|
+
// If "app1" has 2 assessments, you'll get 2 merged rows:
|
|
643
|
+
// - { record: {id: 1, name: "app1"}, assessment: {appId: 1, risk: "high"} }
|
|
644
|
+
// - { record: {id: 1, name: "app1"}, assessment: {appId: 1, risk: "medium"} }
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Notes:**
|
|
648
|
+
- **Index-based merging** is fast but fragile if collections are reordered
|
|
649
|
+
- **Key-based merging** is safer and recommended when you have unique identifiers
|
|
650
|
+
- **Composite-key merging** handles cases where no single unique field exists
|
|
651
|
+
- **SQL-style join types** (`inner`, `left`, `right`, `outer`) provide explicit control over unmatched records
|
|
652
|
+
- **Multiple matches** create multiple rows (SQL-style) - if a key has duplicates, you get one row per match
|
|
653
|
+
- Array fields in composite keys are automatically sorted for consistent matching
|
|
654
|
+
- Supports dot notation for nested fields (e.g., `'meta.id'`, `'ports[]'`)
|
|
655
|
+
- Transaction support available via `session` option
|
|
656
|
+
- Legacy `onUnmatched1`/`onUnmatched2` flags still work but are deprecated in favor of `joinType`
|
|
657
|
+
|
|
658
|
+
### Count Methods
|
|
659
|
+
|
|
660
|
+
#### `countDocuments<T>(collectionName: string, query?: Filter<T>, database?: string): Promise<number>`
|
|
661
|
+
|
|
662
|
+
Counts documents matching a query (accurate count).
|
|
663
|
+
|
|
664
|
+
**Parameters:**
|
|
665
|
+
- `collectionName` - Name of the collection
|
|
666
|
+
- `query` (optional) - MongoDB query filter
|
|
667
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
668
|
+
|
|
669
|
+
**Example:**
|
|
670
|
+
```typescript
|
|
671
|
+
const userCount = await helper.countDocuments('users');
|
|
672
|
+
const activeUserCount = await helper.countDocuments('users', { active: true });
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### `estimatedDocumentCount(collectionName: string): Promise<number>`
|
|
676
|
+
|
|
677
|
+
Gets estimated document count (faster but less accurate).
|
|
678
|
+
|
|
679
|
+
**Example:**
|
|
680
|
+
```typescript
|
|
681
|
+
const estimatedCount = await helper.estimatedDocumentCount('users');
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Aggregation Methods
|
|
685
|
+
|
|
686
|
+
#### `aggregate<T>(collectionName: string, pipeline: Document[], database?: string): Promise<T[]>`
|
|
687
|
+
|
|
688
|
+
Runs an aggregation pipeline on a collection.
|
|
689
|
+
|
|
690
|
+
**Parameters:**
|
|
691
|
+
- `collectionName` - Name of the collection
|
|
692
|
+
- `pipeline` - Array of aggregation pipeline stages
|
|
693
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
694
|
+
|
|
695
|
+
**Example:**
|
|
696
|
+
```typescript
|
|
697
|
+
const result = await helper.aggregate('orders', [
|
|
698
|
+
{ $match: { status: 'completed' } },
|
|
699
|
+
{ $group: {
|
|
700
|
+
_id: '$customerId',
|
|
701
|
+
total: { $sum: '$amount' },
|
|
702
|
+
count: { $sum: 1 }
|
|
703
|
+
}},
|
|
704
|
+
{ $sort: { total: -1 } }
|
|
705
|
+
]);
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Index Methods
|
|
709
|
+
|
|
710
|
+
#### `createIndex(collectionName: string, indexSpec: IndexSpecification, options?: CreateIndexesOptions, database?: string): Promise<string>`
|
|
711
|
+
|
|
712
|
+
Creates an index on a collection.
|
|
713
|
+
|
|
714
|
+
**Parameters:**
|
|
715
|
+
- `collectionName` - Name of the collection
|
|
716
|
+
- `indexSpec` - Index specification
|
|
717
|
+
- `options` (optional) - Index creation options
|
|
718
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
719
|
+
|
|
720
|
+
**Example:**
|
|
721
|
+
```typescript
|
|
722
|
+
// Simple index
|
|
723
|
+
await helper.createIndex('users', { email: 1 });
|
|
724
|
+
|
|
725
|
+
// Unique index
|
|
726
|
+
await helper.createIndex('users', { email: 1 }, { unique: true });
|
|
727
|
+
|
|
728
|
+
// Compound index
|
|
729
|
+
await helper.createIndex('users', { email: 1, createdAt: -1 });
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
#### `dropIndex(collectionName: string, indexName: string, database?: string): Promise<any>`
|
|
733
|
+
|
|
734
|
+
Drops an index from a collection.
|
|
735
|
+
|
|
736
|
+
**Parameters:**
|
|
737
|
+
- `collectionName` - Name of the collection
|
|
738
|
+
- `indexName` - Name of the index to drop
|
|
739
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
740
|
+
|
|
741
|
+
**Example:**
|
|
742
|
+
```typescript
|
|
743
|
+
await helper.dropIndex('users', 'email_1');
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
#### `listIndexes(collectionName: string, database?: string): Promise<Document[]>`
|
|
747
|
+
|
|
748
|
+
Lists all indexes on a collection.
|
|
749
|
+
|
|
750
|
+
**Parameters:**
|
|
751
|
+
- `collectionName` - Name of the collection
|
|
752
|
+
- `database` (optional) - Database name (defaults to `'admin'`)
|
|
753
|
+
|
|
754
|
+
**Example:**
|
|
755
|
+
```typescript
|
|
756
|
+
const indexes = await helper.listIndexes('users');
|
|
757
|
+
indexes.forEach(idx => console.log(idx.name));
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Transaction Methods
|
|
761
|
+
|
|
762
|
+
#### `startSession(): ClientSession`
|
|
763
|
+
|
|
764
|
+
Starts a new client session for transactions.
|
|
765
|
+
|
|
766
|
+
**Example:**
|
|
767
|
+
```typescript
|
|
768
|
+
const session = helper.startSession();
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
#### `withTransaction<T>(callback: (session: ClientSession) => Promise<T>): Promise<T>`
|
|
772
|
+
|
|
773
|
+
Executes a function within a transaction.
|
|
774
|
+
|
|
775
|
+
**Example:**
|
|
776
|
+
```typescript
|
|
777
|
+
await helper.withTransaction(async (session) => {
|
|
778
|
+
await helper.insert('users', { name: 'John' }, { session });
|
|
779
|
+
await helper.update('accounts', { userId: '123' }, { $inc: { balance: 100 } }, { session });
|
|
780
|
+
return 'Transaction completed';
|
|
781
|
+
});
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
**Note:** Transactions require a MongoDB replica set or sharded cluster.
|
|
785
|
+
|
|
786
|
+
## Config-driven Ref Mapping and Signature-based Deduplication
|
|
787
|
+
|
|
788
|
+
### Overview
|
|
789
|
+
|
|
790
|
+
The helper supports config-driven collection mapping and signature-based deduplication. All logic (queries, keys, hashing, append/replace) is generic and built into the helper - applications only pass refs and documents.
|
|
791
|
+
|
|
792
|
+
### Configuration Schema
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
interface HelperConfig {
|
|
796
|
+
inputs: Array<{
|
|
797
|
+
ref: string; // Application-level reference name
|
|
798
|
+
collection: string; // MongoDB collection name
|
|
799
|
+
query?: Filter<any>; // Optional MongoDB query filter
|
|
800
|
+
}>;
|
|
801
|
+
outputs: Array<{
|
|
802
|
+
ref: string; // Application-level reference name
|
|
803
|
+
collection: string; // MongoDB collection name
|
|
804
|
+
keys?: string[]; // Optional: dot-paths for signature generation
|
|
805
|
+
mode?: "append" | "replace"; // Optional: write mode (default from global)
|
|
806
|
+
}>;
|
|
807
|
+
output?: {
|
|
808
|
+
mode?: "append" | "replace"; // Global default mode (default: "append")
|
|
809
|
+
};
|
|
810
|
+
progress?: {
|
|
811
|
+
collection?: string; // Progress collection name (default: "progress_states")
|
|
812
|
+
uniqueIndexKeys?: string[]; // Unique index keys (default: ["provider","key"])
|
|
813
|
+
provider?: string; // Default provider namespace for this helper instance
|
|
814
|
+
};
|
|
815
|
+
databases?: Array<{
|
|
816
|
+
ref: string; // Reference identifier
|
|
817
|
+
type: string; // Type identifier
|
|
818
|
+
database: string; // Database name to use
|
|
819
|
+
}>;
|
|
820
|
+
}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**Example Configuration:**
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
const config = {
|
|
827
|
+
inputs: [
|
|
828
|
+
{ ref: "topology", collection: "topology-definition-neo-data", query: {} },
|
|
829
|
+
{ ref: "vulnerabilities", collection: "vulnerabilities-data", query: { severity: { "$in": ["high","critical"] } } },
|
|
830
|
+
{ ref: "entities", collection: "entities-data" },
|
|
831
|
+
{ ref: "crownJewels", collection: "entities-data", query: { type: "crown_jewel" } }
|
|
832
|
+
],
|
|
833
|
+
outputs: [
|
|
834
|
+
{ ref: "paths", collection: "paths-neo-data", keys: ["segments[]","edges[].from","edges[].to","target_role"], mode: "append" },
|
|
835
|
+
{ ref: "prioritizedPaths", collection: "prioritized_paths-neo-data", keys: ["segments[]","outside","contains_crown_jewel"], mode: "replace" },
|
|
836
|
+
{ ref: "assetPaths", collection: "asset_paths-neo-data", keys: ["asset_ip","segments[]"], mode: "append" }
|
|
837
|
+
],
|
|
838
|
+
output: { mode: "append" }
|
|
839
|
+
};
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### Constructor with Config
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
new SimpleMongoHelper(connectionString: string, retryOptions?: RetryOptions, config?: HelperConfig)
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
**Example:**
|
|
849
|
+
|
|
850
|
+
```typescript
|
|
851
|
+
const helper = new SimpleMongoHelper(
|
|
852
|
+
'mongodb://localhost:27017/my-db',
|
|
853
|
+
{ maxRetries: 5 },
|
|
854
|
+
config
|
|
855
|
+
);
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Config Methods
|
|
859
|
+
|
|
860
|
+
#### `useConfig(config: HelperConfig): this`
|
|
861
|
+
|
|
862
|
+
Sets or updates the configuration for ref-based operations.
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
helper.useConfig(config);
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Database Selection via Ref/Type Map
|
|
869
|
+
|
|
870
|
+
The helper supports config-driven database selection using `ref` and `type` parameters. This allows you to map logical identifiers to database names without hardcoding them in your application code.
|
|
871
|
+
|
|
872
|
+
**Configuration:**
|
|
873
|
+
|
|
874
|
+
```typescript
|
|
875
|
+
const config = {
|
|
876
|
+
// ... inputs, outputs, etc.
|
|
877
|
+
databases: [
|
|
878
|
+
{ ref: "app1", type: "production", database: "app1_prod" },
|
|
879
|
+
{ ref: "app1", type: "staging", database: "app1_staging" },
|
|
880
|
+
{ ref: "app2", type: "production", database: "app2_prod" },
|
|
881
|
+
{ ref: "app2", type: "staging", database: "app2_staging" },
|
|
882
|
+
]
|
|
883
|
+
};
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
**Usage in CRUD Operations:**
|
|
887
|
+
|
|
888
|
+
All CRUD operations now support optional `ref` and `type` parameters for automatic database resolution:
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
// Priority 1: Direct database parameter (highest priority)
|
|
892
|
+
await helper.insert('users', { name: 'John' }, {}, 'mydb');
|
|
893
|
+
|
|
894
|
+
// Priority 2: Using ref + type (exact match)
|
|
895
|
+
await helper.insert('users', { name: 'John' }, {}, undefined, 'app1', 'production');
|
|
896
|
+
// Resolves to 'app1_prod' database
|
|
897
|
+
|
|
898
|
+
// Priority 3: Using ref alone (must have exactly one match)
|
|
899
|
+
await helper.insert('users', { name: 'John' }, {}, undefined, 'app1');
|
|
900
|
+
// Throws error if multiple matches found
|
|
901
|
+
|
|
902
|
+
// Priority 4: Using type alone (must have exactly one match)
|
|
903
|
+
await helper.insert('users', { name: 'John' }, {}, undefined, undefined, 'production');
|
|
904
|
+
// Throws error if multiple matches found
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Database Resolution Priority:**
|
|
908
|
+
|
|
909
|
+
1. **Direct `database` parameter** - If provided, it's used immediately (highest priority)
|
|
910
|
+
2. **`ref` + `type`** - If both provided, finds exact match in databases map
|
|
911
|
+
3. **`ref` alone** - If only ref provided, finds entries matching ref (must be exactly one)
|
|
912
|
+
4. **`type` alone** - If only type provided, finds entries matching type (must be exactly one)
|
|
913
|
+
5. **Default** - If none provided, defaults to `'admin'` database
|
|
914
|
+
|
|
915
|
+
**Error Handling:**
|
|
916
|
+
|
|
917
|
+
- If no match found: throws error with descriptive message
|
|
918
|
+
- If multiple matches found: throws error suggesting to use additional parameter to narrow down
|
|
919
|
+
|
|
920
|
+
**Example:**
|
|
921
|
+
|
|
922
|
+
```typescript
|
|
923
|
+
const config = {
|
|
924
|
+
databases: [
|
|
925
|
+
{ ref: "tenant1", type: "prod", database: "tenant1_prod" },
|
|
926
|
+
{ ref: "tenant1", type: "dev", database: "tenant1_dev" },
|
|
927
|
+
{ ref: "tenant2", type: "prod", database: "tenant2_prod" },
|
|
928
|
+
]
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
const helper = new SimpleMongoHelper('mongodb://localhost:27017/', undefined, config);
|
|
932
|
+
await helper.initialize();
|
|
933
|
+
|
|
934
|
+
// Use ref + type for exact match
|
|
935
|
+
await helper.insert('users', { name: 'John' }, {}, undefined, 'tenant1', 'prod');
|
|
936
|
+
// Uses 'tenant1_prod' database
|
|
937
|
+
|
|
938
|
+
// Use ref alone (only works if exactly one match)
|
|
939
|
+
// This would throw error because tenant1 has 2 matches (prod and dev)
|
|
940
|
+
// await helper.insert('users', { name: 'John' }, {}, undefined, 'tenant1');
|
|
941
|
+
|
|
942
|
+
// Use type alone (only works if exactly one match)
|
|
943
|
+
// This would throw error because 'prod' has 2 matches (tenant1 and tenant2)
|
|
944
|
+
// await helper.insert('users', { name: 'John' }, {}, undefined, undefined, 'prod');
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Ref-based Operations
|
|
948
|
+
|
|
949
|
+
#### `loadByRef<T>(ref: string, options?: PaginationOptions & { session?: ClientSession; database?: string; ref?: string; type?: string }): Promise<WithId<T>[] | PaginatedResult<T>>`
|
|
950
|
+
|
|
951
|
+
Loads data from a collection using a ref name from the configuration.
|
|
952
|
+
|
|
953
|
+
**Parameters:**
|
|
954
|
+
- `ref` - Application-level reference name (must exist in config.inputs)
|
|
955
|
+
- `options` (optional) - Pagination and session options
|
|
956
|
+
- `page?: number` - Page number (1-indexed)
|
|
957
|
+
- `limit?: number` - Documents per page
|
|
958
|
+
- `sort?: Sort` - Sort specification
|
|
959
|
+
- `session?: ClientSession` - Transaction session
|
|
960
|
+
- `database?: string` - Database name (defaults to `'admin'`)
|
|
961
|
+
- `ref?: string` - Optional ref for database resolution
|
|
962
|
+
- `type?: string` - Optional type for database resolution
|
|
963
|
+
|
|
964
|
+
**Example:**
|
|
965
|
+
|
|
966
|
+
```typescript
|
|
967
|
+
// Load using ref (applies query automatically)
|
|
968
|
+
const topology = await helper.loadByRef('topology');
|
|
969
|
+
const vulns = await helper.loadByRef('vulnerabilities');
|
|
970
|
+
|
|
971
|
+
// With pagination
|
|
972
|
+
const result = await helper.loadByRef('topology', {
|
|
973
|
+
page: 1,
|
|
974
|
+
limit: 10,
|
|
975
|
+
sort: { createdAt: -1 }
|
|
976
|
+
});
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
#### `writeByRef(ref: string, documents: any[], options?: { session?: ClientSession; ensureIndex?: boolean; database?: string; ref?: string; type?: string }): Promise<WriteByRefResult>`
|
|
980
|
+
|
|
981
|
+
Writes documents to a collection using a ref name from the configuration. Supports signature-based deduplication and append/replace modes.
|
|
982
|
+
|
|
983
|
+
**Parameters:**
|
|
984
|
+
- `ref` - Application-level reference name (must exist in config.outputs)
|
|
985
|
+
- `documents` - Array of documents to write
|
|
986
|
+
- `options` (optional) - Write options
|
|
987
|
+
- `session?: ClientSession` - Transaction session
|
|
988
|
+
- `ensureIndex?: boolean` - Whether to ensure signature index exists (default: true)
|
|
989
|
+
- `database?: string` - Database name (defaults to `'admin'`)
|
|
990
|
+
- `ref?: string` - Optional ref for database resolution
|
|
991
|
+
- `type?: string` - Optional type for database resolution
|
|
992
|
+
|
|
993
|
+
**Returns:**
|
|
994
|
+
|
|
995
|
+
```typescript
|
|
996
|
+
interface WriteByRefResult {
|
|
997
|
+
inserted: number;
|
|
998
|
+
updated: number;
|
|
999
|
+
errors: Array<{ index: number; error: Error; doc?: any }>;
|
|
1000
|
+
indexCreated: boolean;
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Example:**
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
// Write using ref (automatic deduplication, uses keys from config)
|
|
1008
|
+
const result = await helper.writeByRef('paths', pathDocuments);
|
|
1009
|
+
console.log(`Inserted: ${result.inserted}, Updated: ${result.updated}`);
|
|
1010
|
+
console.log(`Index created: ${result.indexCreated}`);
|
|
1011
|
+
|
|
1012
|
+
// Replace mode (clears collection first)
|
|
1013
|
+
await helper.writeByRef('prioritizedPaths', prioritizedDocs);
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
#### `writeStage(ref: string, documents: any[], options?: WriteStageOptions): Promise<WriteStageResult>`
|
|
1017
|
+
|
|
1018
|
+
Writes documents to a collection and optionally marks a stage as complete atomically. See the [Progress Tracking](#progress-tracking) section for details and examples.
|
|
1019
|
+
|
|
1020
|
+
**Parameters:**
|
|
1021
|
+
- `ref` - Application-level reference name (must exist in config.outputs)
|
|
1022
|
+
- `documents` - Array of documents to write
|
|
1023
|
+
- `options` (optional) - Write and completion options
|
|
1024
|
+
- `session?: ClientSession` - Transaction session
|
|
1025
|
+
- `ensureIndex?: boolean` - Whether to ensure signature index exists (default: true)
|
|
1026
|
+
- `database?: string` - Database name (defaults to `'admin'`)
|
|
1027
|
+
- `complete?: object` - Stage completion information (optional)
|
|
1028
|
+
|
|
1029
|
+
**Example:**
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
// Write and mark stage complete in one call
|
|
1033
|
+
await helper.writeStage('tier1', documents, {
|
|
1034
|
+
complete: {
|
|
1035
|
+
key: 'tier1',
|
|
1036
|
+
process: 'processA',
|
|
1037
|
+
name: 'System Inventory',
|
|
1038
|
+
provider: 'nessus',
|
|
1039
|
+
metadata: { itemCount: documents.length }
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### Signature Index Management
|
|
1045
|
+
|
|
1046
|
+
#### `ensureSignatureIndex(collectionName: string, options?: { fieldName?: string; unique?: boolean }): Promise<EnsureSignatureIndexResult>`
|
|
1047
|
+
|
|
1048
|
+
Ensures a unique index exists on the signature field for signature-based deduplication.
|
|
1049
|
+
|
|
1050
|
+
**Parameters:**
|
|
1051
|
+
- `collectionName` - Name of the collection
|
|
1052
|
+
- `options` (optional) - Index configuration
|
|
1053
|
+
- `fieldName?: string` - Field name for signature (default: "_sig")
|
|
1054
|
+
- `unique?: boolean` - Whether index should be unique (default: true)
|
|
1055
|
+
|
|
1056
|
+
**Returns:**
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
interface EnsureSignatureIndexResult {
|
|
1060
|
+
created: boolean;
|
|
1061
|
+
indexName: string;
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
**Example:**
|
|
1066
|
+
|
|
1067
|
+
```typescript
|
|
1068
|
+
const result = await helper.ensureSignatureIndex('paths-neo-data');
|
|
1069
|
+
console.log(`Index created: ${result.created}, Name: ${result.indexName}`);
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
## Progress Tracking
|
|
1073
|
+
|
|
1074
|
+
### Overview
|
|
1075
|
+
|
|
1076
|
+
The helper provides built-in support for tracking provider-defined pipeline stages. This enables applications to:
|
|
1077
|
+
- Track completion status of different stages (e.g., "tier1", "tier2", "enrichment")
|
|
1078
|
+
- Skip already-completed stages on resumption
|
|
1079
|
+
- Atomically write documents and mark stages complete
|
|
1080
|
+
- Support multi-provider databases with provider namespaces
|
|
1081
|
+
|
|
1082
|
+
### Configuration
|
|
1083
|
+
|
|
1084
|
+
Progress tracking is configured via the `progress` option in `HelperConfig`:
|
|
1085
|
+
|
|
1086
|
+
```typescript
|
|
1087
|
+
const config = {
|
|
1088
|
+
// ... inputs and outputs
|
|
1089
|
+
progress: {
|
|
1090
|
+
collection: "progress_states", // Optional: default "progress_states"
|
|
1091
|
+
uniqueIndexKeys: ["process", "provider", "key"], // Optional: default ["process","provider","key"]
|
|
1092
|
+
provider: "nessus" // Optional: default provider for this instance
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
### Progress API
|
|
1098
|
+
|
|
1099
|
+
The progress API is available via `helper.progress`:
|
|
1100
|
+
|
|
1101
|
+
#### `isCompleted(key: string, options?: { process?: string; provider?: string; session?: ClientSession }): Promise<boolean>`
|
|
1102
|
+
|
|
1103
|
+
Checks if a stage is completed. Stages are scoped by process, so the same key can exist in different processes.
|
|
1104
|
+
|
|
1105
|
+
**Example:**
|
|
1106
|
+
```typescript
|
|
1107
|
+
// Check stage in a specific process
|
|
1108
|
+
if (await helper.progress.isCompleted('tier1', { process: 'processA', provider: 'nessus' })) {
|
|
1109
|
+
console.log('Stage "tier1" in processA already completed, skipping...');
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Same key, different process
|
|
1113
|
+
if (await helper.progress.isCompleted('tier1', { process: 'processB', provider: 'nessus' })) {
|
|
1114
|
+
console.log('Stage "tier1" in processB already completed, skipping...');
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
#### `start(identity: StageIdentity, options?: { session?: ClientSession }): Promise<void>`
|
|
1119
|
+
|
|
1120
|
+
Marks a stage as started. Idempotent - safe to call multiple times. Stages are scoped by process.
|
|
1121
|
+
|
|
1122
|
+
**Example:**
|
|
1123
|
+
```typescript
|
|
1124
|
+
await helper.progress.start({
|
|
1125
|
+
key: 'tier1',
|
|
1126
|
+
process: 'processA',
|
|
1127
|
+
name: 'System Inventory',
|
|
1128
|
+
provider: 'nessus'
|
|
1129
|
+
});
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
#### `complete(identity: StageIdentity & { metadata?: StageMetadata }, options?: { session?: ClientSession }): Promise<void>`
|
|
1133
|
+
|
|
1134
|
+
Marks a stage as completed with optional metadata. Idempotent - safe to call multiple times. Stages are scoped by process.
|
|
1135
|
+
|
|
1136
|
+
**Example:**
|
|
1137
|
+
```typescript
|
|
1138
|
+
await helper.progress.complete({
|
|
1139
|
+
key: 'tier1',
|
|
1140
|
+
process: 'processA',
|
|
1141
|
+
name: 'System Inventory',
|
|
1142
|
+
provider: 'nessus',
|
|
1143
|
+
metadata: {
|
|
1144
|
+
itemCount: 150,
|
|
1145
|
+
durationMs: 5000
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
#### `getCompleted(options?: { process?: string; provider?: string; session?: ClientSession }): Promise<Array<{ key: string; name?: string; completedAt?: Date }>>`
|
|
1151
|
+
|
|
1152
|
+
Gets a list of all completed stages, optionally filtered by process and/or provider.
|
|
1153
|
+
|
|
1154
|
+
**Example:**
|
|
1155
|
+
```typescript
|
|
1156
|
+
// Get all completed stages for a specific process
|
|
1157
|
+
const completed = await helper.progress.getCompleted({ process: 'processA', provider: 'nessus' });
|
|
1158
|
+
// â [{ key: 'tier1', name: 'System Inventory', completedAt: Date }, ...]
|
|
1159
|
+
|
|
1160
|
+
// Get all completed stages across all processes for a provider
|
|
1161
|
+
const allCompleted = await helper.progress.getCompleted({ provider: 'nessus' });
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
#### `getProgress(options?: { process?: string; provider?: string; session?: ClientSession }): Promise<StageRecord[]>`
|
|
1165
|
+
|
|
1166
|
+
Gets all stage records (both completed and in-progress), optionally filtered by process and/or provider.
|
|
1167
|
+
|
|
1168
|
+
**Example:**
|
|
1169
|
+
```typescript
|
|
1170
|
+
// Get all stages for a specific process
|
|
1171
|
+
const allStages = await helper.progress.getProgress({ process: 'processA', provider: 'nessus' });
|
|
1172
|
+
|
|
1173
|
+
// Get all stages for a provider across all processes
|
|
1174
|
+
const allProviderStages = await helper.progress.getProgress({ provider: 'nessus' });
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
#### `reset(key: string, options?: { process?: string; provider?: string; session?: ClientSession }): Promise<void>`
|
|
1178
|
+
|
|
1179
|
+
Resets a stage to not-started state (clears completion status). Stages are scoped by process.
|
|
1180
|
+
|
|
1181
|
+
**Example:**
|
|
1182
|
+
```typescript
|
|
1183
|
+
await helper.progress.reset('tier1', { process: 'processA', provider: 'nessus' });
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
### Stage-Aware Writes
|
|
1187
|
+
|
|
1188
|
+
#### `writeStage(ref: string, documents: any[], options?: WriteStageOptions): Promise<WriteStageResult>`
|
|
1189
|
+
|
|
1190
|
+
Writes documents to a collection and optionally marks a stage as complete in a single call. If a session is provided, both operations are atomic within the transaction.
|
|
1191
|
+
|
|
1192
|
+
**Parameters:**
|
|
1193
|
+
- `ref` - Application-level reference name (must exist in config.outputs)
|
|
1194
|
+
- `documents` - Array of documents to write
|
|
1195
|
+
- `options` (optional) - Write and completion options
|
|
1196
|
+
- `ensureIndex?: boolean` - Whether to ensure signature index exists (default: true)
|
|
1197
|
+
- `session?: ClientSession` - Transaction session (makes write and complete atomic)
|
|
1198
|
+
- `complete?: { key: string; name?: string; provider?: string; metadata?: StageMetadata }` - Stage completion info
|
|
1199
|
+
|
|
1200
|
+
**Returns:**
|
|
1201
|
+
```typescript
|
|
1202
|
+
interface WriteStageResult extends WriteByRefResult {
|
|
1203
|
+
completed?: boolean; // true if stage was marked complete
|
|
1204
|
+
}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
**Examples:**
|
|
1208
|
+
|
|
1209
|
+
```typescript
|
|
1210
|
+
// Skip completed stages, then save-and-complete in one call
|
|
1211
|
+
const processName = 'processA';
|
|
1212
|
+
if (!force && (await helper.progress.isCompleted('tier1', { process: processName, provider: 'nessus' }))) {
|
|
1213
|
+
console.log('Skipping stage "tier1" in processA');
|
|
1214
|
+
} else {
|
|
1215
|
+
const docs = [
|
|
1216
|
+
{ type: 'server_status', ...status },
|
|
1217
|
+
...scanners.map(s => ({ type: 'scanner', ...s }))
|
|
1218
|
+
];
|
|
1219
|
+
await helper.writeStage('tier1', docs, {
|
|
1220
|
+
complete: {
|
|
1221
|
+
key: 'tier1',
|
|
1222
|
+
process: processName,
|
|
1223
|
+
name: 'System Inventory',
|
|
1224
|
+
provider: 'nessus',
|
|
1225
|
+
metadata: { itemCount: docs.length }
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Transactional multi-write with explicit completion
|
|
1231
|
+
const session = helper.startSession();
|
|
1232
|
+
try {
|
|
1233
|
+
await session.withTransaction(async () => {
|
|
1234
|
+
await helper.writeByRef('tier2_scans', scans, { session });
|
|
1235
|
+
await helper.writeByRef('tier2_hosts', hosts, { session });
|
|
1236
|
+
await helper.progress.complete({
|
|
1237
|
+
key: 'tier2',
|
|
1238
|
+
process: 'processA',
|
|
1239
|
+
name: 'Scan Inventory',
|
|
1240
|
+
provider: 'nessus',
|
|
1241
|
+
metadata: { itemCount: hosts.length }
|
|
1242
|
+
}, { session });
|
|
1243
|
+
});
|
|
1244
|
+
} finally {
|
|
1245
|
+
await session.endSession();
|
|
1246
|
+
}
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
### Usage Patterns
|
|
1250
|
+
|
|
1251
|
+
#### Resumption Pattern
|
|
1252
|
+
|
|
1253
|
+
```typescript
|
|
1254
|
+
const processName = 'processA';
|
|
1255
|
+
const stages = ['tier1', 'tier2', 'tier3'];
|
|
1256
|
+
|
|
1257
|
+
for (const stageKey of stages) {
|
|
1258
|
+
if (await helper.progress.isCompleted(stageKey, { process: processName, provider: 'nessus' })) {
|
|
1259
|
+
console.log(`Skipping completed stage: ${stageKey} in ${processName}`);
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
await helper.progress.start({ key: stageKey, process: processName, provider: 'nessus' });
|
|
1264
|
+
|
|
1265
|
+
try {
|
|
1266
|
+
const docs = await processStage(stageKey);
|
|
1267
|
+
await helper.writeStage(`ref_${stageKey}`, docs, {
|
|
1268
|
+
complete: { key: stageKey, process: processName, provider: 'nessus' }
|
|
1269
|
+
});
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
console.error(`Stage ${stageKey} in ${processName} failed:`, error);
|
|
1272
|
+
// Stage remains incomplete, can be retried
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Different process can have same stage keys independently
|
|
1277
|
+
const processB = 'processB';
|
|
1278
|
+
if (!await helper.progress.isCompleted('tier1', { process: processB, provider: 'nessus' })) {
|
|
1279
|
+
// Process B's tier1 is independent from Process A's tier1
|
|
1280
|
+
await helper.progress.start({ key: 'tier1', process: processB, provider: 'nessus' });
|
|
1281
|
+
}
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
### Utility Functions
|
|
1285
|
+
|
|
1286
|
+
#### `getByDotPath(value: any, path: string): any[]`
|
|
1287
|
+
|
|
1288
|
+
Extracts values from an object using dot-notation paths with array wildcard support.
|
|
1289
|
+
|
|
1290
|
+
**Parameters:**
|
|
1291
|
+
- `value` - The object to extract values from
|
|
1292
|
+
- `path` - Dot-notation path (e.g., "meta.id", "edges[].from", "segments[]")
|
|
1293
|
+
|
|
1294
|
+
**Returns:** Array of extracted values (flattened and deduplicated for arrays)
|
|
1295
|
+
|
|
1296
|
+
**Examples:**
|
|
1297
|
+
|
|
1298
|
+
```typescript
|
|
1299
|
+
import { getByDotPath } from 'nx-mongo';
|
|
1300
|
+
|
|
1301
|
+
// Simple path
|
|
1302
|
+
getByDotPath({ meta: { id: "123" } }, "meta.id"); // ["123"]
|
|
1303
|
+
|
|
1304
|
+
// Array wildcard
|
|
1305
|
+
getByDotPath({ segments: [1, 2, 3] }, "segments[]"); // [1, 2, 3]
|
|
1306
|
+
|
|
1307
|
+
// Nested array access
|
|
1308
|
+
getByDotPath({ edges: [{ from: "A" }, { from: "B" }] }, "edges[].from"); // ["A", "B"]
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
#### `computeSignature(doc: any, keys: string[], options?: { algorithm?: "sha256" | "sha1" | "md5" }): string`
|
|
1312
|
+
|
|
1313
|
+
Computes a deterministic signature for a document based on specified keys.
|
|
1314
|
+
|
|
1315
|
+
**Parameters:**
|
|
1316
|
+
- `doc` - The document to compute signature for
|
|
1317
|
+
- `keys` - Array of dot-notation paths to extract values from
|
|
1318
|
+
- `options` (optional) - Configuration
|
|
1319
|
+
- `algorithm?: "sha256" | "sha1" | "md5"` - Hash algorithm (default: "sha256")
|
|
1320
|
+
|
|
1321
|
+
**Returns:** Hex string signature
|
|
1322
|
+
|
|
1323
|
+
**Example:**
|
|
1324
|
+
|
|
1325
|
+
```typescript
|
|
1326
|
+
import { computeSignature } from 'nx-mongo';
|
|
1327
|
+
|
|
1328
|
+
const sig = computeSignature(
|
|
1329
|
+
{ segments: [1, 2], role: "admin" },
|
|
1330
|
+
["segments[]", "role"]
|
|
1331
|
+
);
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
### Signature Algorithm
|
|
1335
|
+
|
|
1336
|
+
The signature generation follows these steps:
|
|
1337
|
+
|
|
1338
|
+
1. **Extract values** for each key using `getByDotPath`
|
|
1339
|
+
2. **Normalize values**:
|
|
1340
|
+
- **Strings**: As-is
|
|
1341
|
+
- **Numbers**: `String(value)`
|
|
1342
|
+
- **Booleans**: `"true"` or `"false"`
|
|
1343
|
+
- **Dates**: `value.toISOString()` (UTC)
|
|
1344
|
+
- **Null/Undefined**: `"null"`
|
|
1345
|
+
- **Objects**: `JSON.stringify(value, Object.keys(value).sort())` (sorted keys)
|
|
1346
|
+
- **Arrays**: Flatten recursively, normalize each element, deduplicate, sort lexicographically
|
|
1347
|
+
3. **Create canonical map**: `{ key1: [normalized values], key2: [normalized values], ... }`
|
|
1348
|
+
4. **Sort keys** alphabetically
|
|
1349
|
+
5. **Stringify**: `JSON.stringify(canonicalMap)`
|
|
1350
|
+
6. **Hash**: SHA-256 (or configurable algorithm)
|
|
1351
|
+
7. **Return**: Hex string
|
|
1352
|
+
|
|
1353
|
+
### Usage Examples
|
|
1354
|
+
|
|
1355
|
+
#### Basic Usage with Config
|
|
1356
|
+
|
|
1357
|
+
```typescript
|
|
1358
|
+
import { SimpleMongoHelper } from 'nx-mongo';
|
|
1359
|
+
|
|
1360
|
+
const config = {
|
|
1361
|
+
inputs: [
|
|
1362
|
+
{ ref: "topology", collection: "topology-definition", query: {} },
|
|
1363
|
+
{ ref: "vulnerabilities", collection: "vulnerabilities", query: { severity: "high" } }
|
|
1364
|
+
],
|
|
1365
|
+
outputs: [
|
|
1366
|
+
{ ref: "paths", collection: "paths", keys: ["segments[]", "target_role"], mode: "append" },
|
|
1367
|
+
{ ref: "prioritizedPaths", collection: "prioritized_paths", keys: ["segments[]"], mode: "replace" }
|
|
1368
|
+
],
|
|
1369
|
+
output: { mode: "append" }
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
const helper = new SimpleMongoHelper('mongodb://localhost:27017/mydb', undefined, config);
|
|
1373
|
+
await helper.initialize();
|
|
1374
|
+
|
|
1375
|
+
// Load using ref (applies query automatically)
|
|
1376
|
+
const topology = await helper.loadByRef('topology');
|
|
1377
|
+
const vulns = await helper.loadByRef('vulnerabilities');
|
|
1378
|
+
|
|
1379
|
+
// Write using ref (automatic deduplication, uses keys from config)
|
|
1380
|
+
const result = await helper.writeByRef('paths', pathDocuments);
|
|
1381
|
+
console.log(`Inserted: ${result.inserted}, Updated: ${result.updated}`);
|
|
1382
|
+
|
|
1383
|
+
// Replace mode (clears collection first)
|
|
1384
|
+
await helper.writeByRef('prioritizedPaths', prioritizedDocs);
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
#### With Transactions
|
|
1388
|
+
|
|
1389
|
+
```typescript
|
|
1390
|
+
const session = helper.startSession();
|
|
1391
|
+
try {
|
|
1392
|
+
await session.withTransaction(async () => {
|
|
1393
|
+
await helper.writeByRef('paths', docs, { session });
|
|
1394
|
+
await helper.writeByRef('prioritizedPaths', prioDocs, { session });
|
|
1395
|
+
});
|
|
1396
|
+
} finally {
|
|
1397
|
+
await session.endSession();
|
|
1398
|
+
}
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
#### Standalone Utilities
|
|
1402
|
+
|
|
1403
|
+
```typescript
|
|
1404
|
+
import { getByDotPath, computeSignature } from 'nx-mongo';
|
|
1405
|
+
|
|
1406
|
+
// Extract values
|
|
1407
|
+
const values = getByDotPath(doc, "edges[].from"); // ["A", "B", "C"]
|
|
1408
|
+
|
|
1409
|
+
// Compute signature
|
|
1410
|
+
const sig = computeSignature(doc, ["segments[]", "target_role"]);
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
## TypeScript Interfaces
|
|
1414
|
+
|
|
1415
|
+
### PaginationOptions
|
|
1416
|
+
|
|
1417
|
+
```typescript
|
|
1418
|
+
interface PaginationOptions {
|
|
1419
|
+
page?: number;
|
|
1420
|
+
limit?: number;
|
|
1421
|
+
sort?: Sort;
|
|
1422
|
+
}
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
### PaginatedResult
|
|
1426
|
+
|
|
1427
|
+
```typescript
|
|
1428
|
+
interface PaginatedResult<T> {
|
|
1429
|
+
data: WithId<T>[];
|
|
1430
|
+
total: number;
|
|
1431
|
+
page: number;
|
|
1432
|
+
limit: number;
|
|
1433
|
+
totalPages: number;
|
|
1434
|
+
hasNext: boolean;
|
|
1435
|
+
hasPrev: boolean;
|
|
1436
|
+
}
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
### RetryOptions
|
|
1440
|
+
|
|
1441
|
+
```typescript
|
|
1442
|
+
interface RetryOptions {
|
|
1443
|
+
maxRetries?: number;
|
|
1444
|
+
retryDelay?: number;
|
|
1445
|
+
exponentialBackoff?: boolean;
|
|
1446
|
+
}
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
### HelperConfig
|
|
1450
|
+
|
|
1451
|
+
```typescript
|
|
1452
|
+
interface HelperConfig {
|
|
1453
|
+
inputs: InputConfig[];
|
|
1454
|
+
outputs: OutputConfig[];
|
|
1455
|
+
output?: {
|
|
1456
|
+
mode?: 'append' | 'replace';
|
|
1457
|
+
};
|
|
1458
|
+
progress?: {
|
|
1459
|
+
collection?: string;
|
|
1460
|
+
uniqueIndexKeys?: string[];
|
|
1461
|
+
provider?: string;
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
interface InputConfig {
|
|
1466
|
+
ref: string;
|
|
1467
|
+
collection: string;
|
|
1468
|
+
query?: Filter<any>;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
interface OutputConfig {
|
|
1472
|
+
ref: string;
|
|
1473
|
+
collection: string;
|
|
1474
|
+
keys?: string[];
|
|
1475
|
+
mode?: 'append' | 'replace';
|
|
1476
|
+
}
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
### WriteByRefResult
|
|
1480
|
+
|
|
1481
|
+
```typescript
|
|
1482
|
+
interface WriteByRefResult {
|
|
1483
|
+
inserted: number;
|
|
1484
|
+
updated: number;
|
|
1485
|
+
errors: Array<{ index: number; error: Error; doc?: any }>;
|
|
1486
|
+
indexCreated: boolean;
|
|
1487
|
+
}
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
### EnsureSignatureIndexResult
|
|
1491
|
+
|
|
1492
|
+
```typescript
|
|
1493
|
+
interface EnsureSignatureIndexResult {
|
|
1494
|
+
created: boolean;
|
|
1495
|
+
indexName: string;
|
|
1496
|
+
}
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
### Progress Tracking Interfaces
|
|
1500
|
+
|
|
1501
|
+
```typescript
|
|
1502
|
+
interface StageIdentity {
|
|
1503
|
+
key: string;
|
|
1504
|
+
process?: string;
|
|
1505
|
+
provider?: string;
|
|
1506
|
+
name?: string;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
interface StageMetadata {
|
|
1510
|
+
itemCount?: number;
|
|
1511
|
+
errorCount?: number;
|
|
1512
|
+
durationMs?: number;
|
|
1513
|
+
[key: string]: any;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
interface StageRecord extends StageIdentity {
|
|
1517
|
+
completed: boolean;
|
|
1518
|
+
startedAt?: Date;
|
|
1519
|
+
completedAt?: Date;
|
|
1520
|
+
metadata?: StageMetadata;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
interface WriteStageOptions {
|
|
1524
|
+
ensureIndex?: boolean;
|
|
1525
|
+
session?: ClientSession;
|
|
1526
|
+
complete?: {
|
|
1527
|
+
key: string;
|
|
1528
|
+
process?: string;
|
|
1529
|
+
name?: string;
|
|
1530
|
+
provider?: string;
|
|
1531
|
+
metadata?: StageMetadata;
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
interface WriteStageResult extends WriteByRefResult {
|
|
1536
|
+
completed?: boolean;
|
|
1537
|
+
}
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
### Merge Collections Interfaces
|
|
1541
|
+
|
|
1542
|
+
```typescript
|
|
1543
|
+
interface MergeCollectionsOptions {
|
|
1544
|
+
sourceCollection1: string;
|
|
1545
|
+
sourceCollection2: string;
|
|
1546
|
+
targetCollection: string;
|
|
1547
|
+
strategy: 'index' | 'key' | 'composite';
|
|
1548
|
+
key?: string;
|
|
1549
|
+
compositeKeys?: string[];
|
|
1550
|
+
joinType?: 'inner' | 'left' | 'right' | 'outer'; // SQL-style join type
|
|
1551
|
+
fieldPrefix1?: string;
|
|
1552
|
+
fieldPrefix2?: string;
|
|
1553
|
+
includeIndex?: boolean;
|
|
1554
|
+
onUnmatched1?: 'include' | 'skip'; // Deprecated: use joinType instead
|
|
1555
|
+
onUnmatched2?: 'include' | 'skip'; // Deprecated: use joinType instead
|
|
1556
|
+
session?: ClientSession;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
interface MergeCollectionsResult {
|
|
1560
|
+
merged: number;
|
|
1561
|
+
unmatched1: number;
|
|
1562
|
+
unmatched2: number;
|
|
1563
|
+
errors: Array<{ index: number; error: Error; doc?: any }>;
|
|
1564
|
+
}
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
## Error Handling
|
|
1568
|
+
|
|
1569
|
+
All methods throw errors with descriptive messages. Always wrap operations in try-catch blocks:
|
|
1570
|
+
|
|
1571
|
+
```typescript
|
|
1572
|
+
try {
|
|
1573
|
+
await helper.initialize();
|
|
1574
|
+
const users = await helper.loadCollection('users');
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
console.error('Operation failed:', error.message);
|
|
1577
|
+
}
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
## Best Practices
|
|
1581
|
+
|
|
1582
|
+
1. **Always initialize before use:**
|
|
1583
|
+
```typescript
|
|
1584
|
+
await helper.initialize();
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
2. **Use transactions for multi-operation consistency:**
|
|
1588
|
+
```typescript
|
|
1589
|
+
await helper.withTransaction(async (session) => {
|
|
1590
|
+
// Multiple operations
|
|
1591
|
+
});
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
3. **Use pagination for large datasets:**
|
|
1595
|
+
```typescript
|
|
1596
|
+
const result = await helper.loadCollection('users', {}, { page: 1, limit: 50 });
|
|
1597
|
+
```
|
|
1598
|
+
|
|
1599
|
+
4. **Create indexes for frequently queried fields:**
|
|
1600
|
+
```typescript
|
|
1601
|
+
await helper.createIndex('users', { email: 1 }, { unique: true });
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
5. **Disconnect when done (optional but recommended):**
|
|
1605
|
+
```typescript
|
|
1606
|
+
await helper.disconnect();
|
|
1607
|
+
```
|
|
1608
|
+
**Note:** Connections automatically close on app exit (SIGINT/SIGTERM), but explicit disconnection is recommended for better control and immediate cleanup.
|
|
1609
|
+
|
|
1610
|
+
## ERC 2.0 Compliance
|
|
1611
|
+
|
|
1612
|
+
â
**Auto-discovers configuration from environment variables**
|
|
1613
|
+
â
**Type-safe with automatic coercion and validation**
|
|
1614
|
+
â
**All dependency requirements documented**
|
|
1615
|
+
â
**Transitive requirements automatically merged**
|
|
1616
|
+
|
|
1617
|
+
**Dependencies:**
|
|
1618
|
+
- â
`nx-config2` (Configuration engine)
|
|
1619
|
+
- âšī¸ `mongodb` (non-ERC) - requirements manually documented
|
|
1620
|
+
- âšī¸ `micro-logs` (non-ERC) - no environment variables required
|
|
1621
|
+
|
|
1622
|
+
**Verification:**
|
|
1623
|
+
```bash
|
|
1624
|
+
npx nx-config2 erc-verify
|
|
1625
|
+
```
|
|
1626
|
+
|
|
1627
|
+
## License
|
|
1628
|
+
|
|
1629
|
+
ISC
|
|
1630
|
+
|
|
1631
|
+
## Contributing
|
|
1632
|
+
|
|
1633
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1634
|
+
|
|
1635
|
+
## Changelog
|
|
1636
|
+
|
|
1637
|
+
### 4.0.1
|
|
1638
|
+
- **ES Module Support**: Converted package to ES module format (`"type": "module"`)
|
|
1639
|
+
- Fixed compatibility issue with ES module dependencies (e.g., `micro-logs`)
|
|
1640
|
+
- Updated TypeScript configuration to use `NodeNext` module resolution
|
|
1641
|
+
- Updated package exports to properly support ES module imports
|
|
1642
|
+
- **Breaking Change**: Package now uses ES module syntax; CommonJS `require()` is no longer supported
|
|
1643
|
+
|
|
1644
|
+
### 4.0.0
|
|
1645
|
+
- **ERC 2.0 Compliance**: Added zero-config initialization via environment variables
|
|
1646
|
+
- Integrated `nx-config2` with ERC mode for automatic configuration discovery
|
|
1647
|
+
- Added automatic manifest and `.env.example` generation
|
|
1648
|
+
- Support for both zero-config and advanced (programmatic) configuration modes
|
|
1649
|
+
|
|
1650
|
+
### 3.8.1
|
|
1651
|
+
- **Fixed `testConnection()` error reporting bug**: Improved error extraction from MongoDB error objects to prevent `[object Object]` display
|
|
1652
|
+
- Enhanced error handling to extract MongoDB-specific error properties (`code`, `codeName`, `errmsg`)
|
|
1653
|
+
- Added Windows-specific troubleshooting hints for localhost connection issues (suggests using `127.0.0.1` instead of `localhost`)
|
|
1654
|
+
- Improved error messages with error codes and types for better debugging
|
|
1655
|
+
- Updated documentation with better error handling examples
|
|
1656
|
+
|
|
1657
|
+
### 3.8.0
|
|
1658
|
+
- **Database selection via ref/type map**: Added config-driven database selection using `ref` and `type` parameters
|
|
1659
|
+
- Added `databases` array to `HelperConfig` for mapping ref/type combinations to database names
|
|
1660
|
+
- All CRUD operations now support optional `ref` and `type` parameters for automatic database resolution
|
|
1661
|
+
- Database resolution priority: direct `database` parameter > `ref` + `type` > `ref` alone > `type` alone
|
|
1662
|
+
- Throws descriptive errors when no match or multiple matches found in database map
|
|
1663
|
+
- Updated Progress API to support database resolution via ref/type
|
|
1664
|
+
- Updated `loadByRef`, `writeByRef`, `writeStage`, and `mergeCollections` to support database map resolution
|
|
1665
|
+
|
|
1666
|
+
### 3.7.0
|
|
1667
|
+
- **Separated database from connection string**: Database name is now specified per operation, not in connection string
|
|
1668
|
+
- **Multi-database support**: All operations accept optional `database` parameter (defaults to `'admin'`)
|
|
1669
|
+
- Connection string database name is automatically stripped if present (e.g., `mongodb://localhost:27017/admin` becomes `mongodb://localhost:27017/`)
|
|
1670
|
+
- Updated all methods (`insert`, `update`, `delete`, `loadCollection`, `findOne`, `countDocuments`, `aggregate`, `createIndex`, `dropIndex`, `listIndexes`, `writeByRef`, `loadByRef`, `mergeCollections`, `writeStage`, and progress API) to support per-operation database selection
|
|
1671
|
+
- **Breaking change**: No backward compatibility - all code must be updated to use new database parameter
|
|
1672
|
+
|
|
1673
|
+
### 3.6.0
|
|
1674
|
+
- **Automatic connection cleanup**: Connections now automatically close on app exit (SIGINT, SIGTERM, beforeExit)
|
|
1675
|
+
- **Multi-instance support**: Global registry handles multiple `SimpleMongoHelper` instances gracefully
|
|
1676
|
+
- **Timeout protection**: 5-second timeout prevents hanging during automatic cleanup
|
|
1677
|
+
- Connections are properly managed and cleaned up even if users forget to call `disconnect()`
|
|
1678
|
+
|
|
1679
|
+
### 3.5.0
|
|
1680
|
+
- Enhanced `mergeCollections()` with SQL-style join types (`inner`, `left`, `right`, `outer`)
|
|
1681
|
+
- **Multiple match handling**: Now creates multiple rows when keys have duplicates (SQL-style behavior)
|
|
1682
|
+
- Improved key-based and composite-key merging to handle one-to-many and many-to-many relationships
|
|
1683
|
+
- Added explicit join type control for better clarity and SQL compatibility
|
|
1684
|
+
- Legacy `onUnmatched1`/`onUnmatched2` flags deprecated in favor of `joinType` parameter
|
|
1685
|
+
|
|
1686
|
+
### 3.4.0
|
|
1687
|
+
- Added `mergeCollections()` method for merging two collections into a new target collection
|
|
1688
|
+
- Supports three merge strategies: index-based, key-based, and composite-key merging
|
|
1689
|
+
- Index-based merging for same-order collections
|
|
1690
|
+
- Key-based merging using unique identifiers (supports dot notation)
|
|
1691
|
+
- Composite-key merging using multiple fields (e.g., name + ports + zones)
|
|
1692
|
+
- Configurable field prefixes and unmatched record handling
|
|
1693
|
+
- Transaction support for atomic merge operations
|
|
1694
|
+
|
|
1695
|
+
### 3.3.0
|
|
1696
|
+
- Added `testConnection()` method for detailed connection testing and error diagnostics
|
|
1697
|
+
- Package renamed from `nx-mongodb-helper` to `nx-mongo` (shorter, cleaner name)
|
|
1698
|
+
- Connection test provides detailed error messages for missing credentials, invalid connection strings, authentication failures, and network issues
|
|
1699
|
+
|
|
1700
|
+
### 3.2.0
|
|
1701
|
+
- Added process-scoped stages support - stages can now be scoped by process identifier
|
|
1702
|
+
- Updated default unique index to include `process` field: `['process', 'provider', 'key']`
|
|
1703
|
+
- All ProgressAPI methods now accept `process` parameter for process-scoped stage tracking
|
|
1704
|
+
- Updated `writeStage()` to support process-scoped completion
|
|
1705
|
+
- Stages with the same key can exist independently in different processes
|
|
1706
|
+
|
|
1707
|
+
### 3.1.0
|
|
1708
|
+
- Added built-in progress tracking API (`helper.progress`) for provider-defined pipeline stages
|
|
1709
|
+
- Added `writeStage()` method that combines document writing with stage completion
|
|
1710
|
+
- Added progress tracking configuration to `HelperConfig` (collection, uniqueIndexKeys, provider)
|
|
1711
|
+
- Progress API supports idempotent operations, transactions, and provider namespaces
|
|
1712
|
+
- All progress operations support optional transaction sessions for atomicity
|
|
1713
|
+
|
|
1714
|
+
### 3.0.0
|
|
1715
|
+
- Added config-driven ref mapping (HelperConfig, InputConfig, OutputConfig)
|
|
1716
|
+
- Added signature-based deduplication with automatic index management
|
|
1717
|
+
- Added `loadByRef()` method for loading data by ref name
|
|
1718
|
+
- Added `writeByRef()` method with signature computation, bulk upsert, and append/replace modes
|
|
1719
|
+
- Added `ensureSignatureIndex()` method for signature index management
|
|
1720
|
+
- Added `useConfig()` method for runtime config updates
|
|
1721
|
+
- Added `getByDotPath()` utility function for dot-notation path extraction with array wildcards
|
|
1722
|
+
- Added `computeSignature()` utility function for deterministic document signatures
|
|
1723
|
+
- Enhanced constructor to accept optional config parameter
|
|
1724
|
+
- All new methods support transaction sessions
|
|
1725
|
+
|
|
1726
|
+
### 2.0.1
|
|
1727
|
+
- Package renamed from `nx-mongodb-helper` to `nx-mongo`
|
|
1728
|
+
- Add version number to README header
|
|
1729
|
+
|
|
1730
|
+
### 2.0.0
|
|
1731
|
+
- Added delete operations
|
|
1732
|
+
- Added findOne operation
|
|
1733
|
+
- Added count operations (countDocuments, estimatedDocumentCount)
|
|
1734
|
+
- Added pagination support
|
|
1735
|
+
- Added aggregation pipeline support
|
|
1736
|
+
- Added transaction support
|
|
1737
|
+
- Added connection retry logic with exponential backoff
|
|
1738
|
+
- Added index management (createIndex, dropIndex, listIndexes)
|
|
1739
|
+
- Enhanced insert and update methods with session support
|
|
1740
|
+
|
|
1741
|
+
### 1.0.0
|
|
1742
|
+
- Initial release with basic CRUD operations
|
|
1743
|
+
|