nx-mongo 3.5.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -21
- package/dist/simpleMongoHelper.d.ts +79 -16
- package/dist/simpleMongoHelper.d.ts.map +1 -1
- package/dist/simpleMongoHelper.js +279 -61
- package/dist/simpleMongoHelper.js.map +1 -1
- package/package.json +1 -1
- package/src/simpleMongoHelper.ts +347 -62
|
@@ -195,13 +195,14 @@ class ProgressAPIImpl {
|
|
|
195
195
|
* Ensures the progress collection and unique index exist.
|
|
196
196
|
* Called lazily on first use.
|
|
197
197
|
*/
|
|
198
|
-
async ensureProgressIndex(session) {
|
|
198
|
+
async ensureProgressIndex(session, database, ref, type) {
|
|
199
199
|
if (this.indexEnsured) {
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
this.helper.ensureInitialized();
|
|
203
203
|
try {
|
|
204
|
-
const
|
|
204
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
205
|
+
const collection = db.collection(this.config.collection);
|
|
205
206
|
const indexes = await collection.indexes();
|
|
206
207
|
// Build index spec from uniqueIndexKeys
|
|
207
208
|
const indexSpec = {};
|
|
@@ -253,12 +254,16 @@ class ProgressAPIImpl {
|
|
|
253
254
|
return filter;
|
|
254
255
|
}
|
|
255
256
|
async isCompleted(key, options) {
|
|
256
|
-
|
|
257
|
+
const database = options?.database;
|
|
258
|
+
const ref = options?.ref;
|
|
259
|
+
const type = options?.type;
|
|
260
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
257
261
|
this.helper.ensureInitialized();
|
|
258
262
|
const provider = this.resolveProvider(options);
|
|
259
263
|
const process = options?.process;
|
|
260
264
|
const filter = this.buildFilter(key, process, provider);
|
|
261
|
-
const
|
|
265
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
266
|
+
const collection = db.collection(this.config.collection);
|
|
262
267
|
const findOptions = {};
|
|
263
268
|
if (options?.session) {
|
|
264
269
|
findOptions.session = options.session;
|
|
@@ -267,12 +272,16 @@ class ProgressAPIImpl {
|
|
|
267
272
|
return record?.completed === true;
|
|
268
273
|
}
|
|
269
274
|
async start(identity, options) {
|
|
270
|
-
|
|
275
|
+
const database = options?.database;
|
|
276
|
+
const ref = options?.ref;
|
|
277
|
+
const type = options?.type;
|
|
278
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
271
279
|
this.helper.ensureInitialized();
|
|
272
280
|
const provider = this.resolveProvider({ provider: identity.provider });
|
|
273
281
|
const process = identity.process;
|
|
274
282
|
const filter = this.buildFilter(identity.key, process, provider);
|
|
275
|
-
const
|
|
283
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
284
|
+
const collection = db.collection(this.config.collection);
|
|
276
285
|
const update = {
|
|
277
286
|
$set: {
|
|
278
287
|
key: identity.key,
|
|
@@ -296,12 +305,16 @@ class ProgressAPIImpl {
|
|
|
296
305
|
await collection.updateOne(filter, update, updateOptions);
|
|
297
306
|
}
|
|
298
307
|
async complete(identity, options) {
|
|
299
|
-
|
|
308
|
+
const database = options?.database;
|
|
309
|
+
const ref = options?.ref;
|
|
310
|
+
const type = options?.type;
|
|
311
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
300
312
|
this.helper.ensureInitialized();
|
|
301
313
|
const provider = this.resolveProvider({ provider: identity.provider });
|
|
302
314
|
const process = identity.process;
|
|
303
315
|
const filter = this.buildFilter(identity.key, process, provider);
|
|
304
|
-
const
|
|
316
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
317
|
+
const collection = db.collection(this.config.collection);
|
|
305
318
|
const update = {
|
|
306
319
|
$set: {
|
|
307
320
|
key: identity.key,
|
|
@@ -329,7 +342,10 @@ class ProgressAPIImpl {
|
|
|
329
342
|
await collection.updateOne(filter, update, updateOptions);
|
|
330
343
|
}
|
|
331
344
|
async getCompleted(options) {
|
|
332
|
-
|
|
345
|
+
const database = options?.database;
|
|
346
|
+
const ref = options?.ref;
|
|
347
|
+
const type = options?.type;
|
|
348
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
333
349
|
this.helper.ensureInitialized();
|
|
334
350
|
const provider = this.resolveProvider(options);
|
|
335
351
|
const process = options?.process;
|
|
@@ -340,7 +356,8 @@ class ProgressAPIImpl {
|
|
|
340
356
|
if (provider !== undefined) {
|
|
341
357
|
filter.provider = provider;
|
|
342
358
|
}
|
|
343
|
-
const
|
|
359
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
360
|
+
const collection = db.collection(this.config.collection);
|
|
344
361
|
const findOptions = { projection: { key: 1, name: 1, completedAt: 1 } };
|
|
345
362
|
if (options?.session) {
|
|
346
363
|
findOptions.session = options.session;
|
|
@@ -353,7 +370,10 @@ class ProgressAPIImpl {
|
|
|
353
370
|
}));
|
|
354
371
|
}
|
|
355
372
|
async getProgress(options) {
|
|
356
|
-
|
|
373
|
+
const database = options?.database;
|
|
374
|
+
const ref = options?.ref;
|
|
375
|
+
const type = options?.type;
|
|
376
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
357
377
|
this.helper.ensureInitialized();
|
|
358
378
|
const provider = this.resolveProvider(options);
|
|
359
379
|
const process = options?.process;
|
|
@@ -364,7 +384,8 @@ class ProgressAPIImpl {
|
|
|
364
384
|
if (provider !== undefined) {
|
|
365
385
|
filter.provider = provider;
|
|
366
386
|
}
|
|
367
|
-
const
|
|
387
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
388
|
+
const collection = db.collection(this.config.collection);
|
|
368
389
|
const findOptions = {};
|
|
369
390
|
if (options?.session) {
|
|
370
391
|
findOptions.session = options.session;
|
|
@@ -372,12 +393,16 @@ class ProgressAPIImpl {
|
|
|
372
393
|
return await collection.find(filter, findOptions).toArray();
|
|
373
394
|
}
|
|
374
395
|
async reset(key, options) {
|
|
375
|
-
|
|
396
|
+
const database = options?.database;
|
|
397
|
+
const ref = options?.ref;
|
|
398
|
+
const type = options?.type;
|
|
399
|
+
await this.ensureProgressIndex(options?.session, database, ref, type);
|
|
376
400
|
this.helper.ensureInitialized();
|
|
377
401
|
const provider = this.resolveProvider(options);
|
|
378
402
|
const process = options?.process;
|
|
379
403
|
const filter = this.buildFilter(key, process, provider);
|
|
380
|
-
const
|
|
404
|
+
const db = this.helper.getDatabaseByName(database, ref, type);
|
|
405
|
+
const collection = db.collection(this.config.collection);
|
|
381
406
|
const update = {
|
|
382
407
|
$set: {
|
|
383
408
|
completed: false,
|
|
@@ -400,7 +425,10 @@ class SimpleMongoHelper {
|
|
|
400
425
|
this.db = null;
|
|
401
426
|
this.isInitialized = false;
|
|
402
427
|
this.config = null;
|
|
403
|
-
this.
|
|
428
|
+
this.cleanupRegistered = false;
|
|
429
|
+
this.isDisconnecting = false;
|
|
430
|
+
// Strip database name from connection string if present
|
|
431
|
+
this.connectionString = this.stripDatabaseFromConnectionString(connectionString);
|
|
404
432
|
this.retryOptions = {
|
|
405
433
|
maxRetries: retryOptions?.maxRetries ?? 3,
|
|
406
434
|
retryDelay: retryOptions?.retryDelay ?? 1000,
|
|
@@ -408,6 +436,8 @@ class SimpleMongoHelper {
|
|
|
408
436
|
};
|
|
409
437
|
this.config = config || null;
|
|
410
438
|
this.progress = new ProgressAPIImpl(this, config?.progress);
|
|
439
|
+
// Register automatic cleanup on process exit
|
|
440
|
+
this.registerCleanup();
|
|
411
441
|
}
|
|
412
442
|
/**
|
|
413
443
|
* Sets or updates the configuration for ref-based operations.
|
|
@@ -501,9 +531,8 @@ class SimpleMongoHelper {
|
|
|
501
531
|
connectTimeoutMS: 5000
|
|
502
532
|
});
|
|
503
533
|
await testClient.connect();
|
|
504
|
-
// Try to ping the server
|
|
505
|
-
const
|
|
506
|
-
const testDb = testClient.db(dbName);
|
|
534
|
+
// Try to ping the server using 'admin' database (default)
|
|
535
|
+
const testDb = testClient.db('admin');
|
|
507
536
|
await testDb.admin().ping();
|
|
508
537
|
// Test basic operation (list collections)
|
|
509
538
|
try {
|
|
@@ -623,9 +652,8 @@ class SimpleMongoHelper {
|
|
|
623
652
|
try {
|
|
624
653
|
this.client = new mongodb_1.MongoClient(this.connectionString);
|
|
625
654
|
await this.client.connect();
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
this.db = this.client.db(dbName);
|
|
655
|
+
// Default to 'admin' database for initial connection (not used for operations)
|
|
656
|
+
this.db = this.client.db('admin');
|
|
629
657
|
this.isInitialized = true;
|
|
630
658
|
return;
|
|
631
659
|
}
|
|
@@ -651,10 +679,11 @@ class SimpleMongoHelper {
|
|
|
651
679
|
* @returns Array of documents matching the query or paginated result
|
|
652
680
|
* @throws Error if not initialized or if query fails
|
|
653
681
|
*/
|
|
654
|
-
async loadCollection(collectionName, query, options) {
|
|
682
|
+
async loadCollection(collectionName, query, options, database, ref, type) {
|
|
655
683
|
this.ensureInitialized();
|
|
656
684
|
try {
|
|
657
|
-
const
|
|
685
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
686
|
+
const collection = db.collection(collectionName);
|
|
658
687
|
const filter = query || {};
|
|
659
688
|
let cursor = collection.find(filter);
|
|
660
689
|
// Apply sorting if provided
|
|
@@ -697,10 +726,11 @@ class SimpleMongoHelper {
|
|
|
697
726
|
* @returns Single document matching the query or null
|
|
698
727
|
* @throws Error if not initialized or if query fails
|
|
699
728
|
*/
|
|
700
|
-
async findOne(collectionName, query, options) {
|
|
729
|
+
async findOne(collectionName, query, options, database, ref, type) {
|
|
701
730
|
this.ensureInitialized();
|
|
702
731
|
try {
|
|
703
|
-
const
|
|
732
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
733
|
+
const collection = db.collection(collectionName);
|
|
704
734
|
let findOptions = {};
|
|
705
735
|
if (options?.sort) {
|
|
706
736
|
findOptions.sort = options.sort;
|
|
@@ -723,10 +753,11 @@ class SimpleMongoHelper {
|
|
|
723
753
|
* @returns Insert result(s)
|
|
724
754
|
* @throws Error if not initialized or if insert fails
|
|
725
755
|
*/
|
|
726
|
-
async insert(collectionName, data, options) {
|
|
756
|
+
async insert(collectionName, data, options, database, ref, type) {
|
|
727
757
|
this.ensureInitialized();
|
|
728
758
|
try {
|
|
729
|
-
const
|
|
759
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
760
|
+
const collection = db.collection(collectionName);
|
|
730
761
|
const insertOptions = options?.session ? { session: options.session } : {};
|
|
731
762
|
if (Array.isArray(data)) {
|
|
732
763
|
const result = await collection.insertMany(data, insertOptions);
|
|
@@ -750,10 +781,11 @@ class SimpleMongoHelper {
|
|
|
750
781
|
* @returns Update result
|
|
751
782
|
* @throws Error if not initialized or if update fails
|
|
752
783
|
*/
|
|
753
|
-
async update(collectionName, filter, updateData, options) {
|
|
784
|
+
async update(collectionName, filter, updateData, options, database, ref, type) {
|
|
754
785
|
this.ensureInitialized();
|
|
755
786
|
try {
|
|
756
|
-
const
|
|
787
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
788
|
+
const collection = db.collection(collectionName);
|
|
757
789
|
const updateOptions = {};
|
|
758
790
|
if (options?.upsert !== undefined)
|
|
759
791
|
updateOptions.upsert = options.upsert;
|
|
@@ -780,10 +812,11 @@ class SimpleMongoHelper {
|
|
|
780
812
|
* @returns Delete result
|
|
781
813
|
* @throws Error if not initialized or if delete fails
|
|
782
814
|
*/
|
|
783
|
-
async delete(collectionName, filter, options) {
|
|
815
|
+
async delete(collectionName, filter, options, database, ref, type) {
|
|
784
816
|
this.ensureInitialized();
|
|
785
817
|
try {
|
|
786
|
-
const
|
|
818
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
819
|
+
const collection = db.collection(collectionName);
|
|
787
820
|
if (options?.multi) {
|
|
788
821
|
const result = await collection.deleteMany(filter);
|
|
789
822
|
return result;
|
|
@@ -804,10 +837,11 @@ class SimpleMongoHelper {
|
|
|
804
837
|
* @returns Number of documents matching the query
|
|
805
838
|
* @throws Error if not initialized or if count fails
|
|
806
839
|
*/
|
|
807
|
-
async countDocuments(collectionName, query) {
|
|
840
|
+
async countDocuments(collectionName, query, database, ref, type) {
|
|
808
841
|
this.ensureInitialized();
|
|
809
842
|
try {
|
|
810
|
-
const
|
|
843
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
844
|
+
const collection = db.collection(collectionName);
|
|
811
845
|
const count = await collection.countDocuments(query || {});
|
|
812
846
|
return count;
|
|
813
847
|
}
|
|
@@ -821,10 +855,11 @@ class SimpleMongoHelper {
|
|
|
821
855
|
* @returns Estimated number of documents
|
|
822
856
|
* @throws Error if not initialized or if count fails
|
|
823
857
|
*/
|
|
824
|
-
async estimatedDocumentCount(collectionName) {
|
|
858
|
+
async estimatedDocumentCount(collectionName, database, ref, type) {
|
|
825
859
|
this.ensureInitialized();
|
|
826
860
|
try {
|
|
827
|
-
const
|
|
861
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
862
|
+
const collection = db.collection(collectionName);
|
|
828
863
|
const count = await collection.estimatedDocumentCount();
|
|
829
864
|
return count;
|
|
830
865
|
}
|
|
@@ -839,10 +874,11 @@ class SimpleMongoHelper {
|
|
|
839
874
|
* @returns Array of aggregated results
|
|
840
875
|
* @throws Error if not initialized or if aggregation fails
|
|
841
876
|
*/
|
|
842
|
-
async aggregate(collectionName, pipeline) {
|
|
877
|
+
async aggregate(collectionName, pipeline, database, ref, type) {
|
|
843
878
|
this.ensureInitialized();
|
|
844
879
|
try {
|
|
845
|
-
const
|
|
880
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
881
|
+
const collection = db.collection(collectionName);
|
|
846
882
|
const results = await collection.aggregate(pipeline).toArray();
|
|
847
883
|
return results;
|
|
848
884
|
}
|
|
@@ -858,10 +894,11 @@ class SimpleMongoHelper {
|
|
|
858
894
|
* @returns Index name
|
|
859
895
|
* @throws Error if not initialized or if index creation fails
|
|
860
896
|
*/
|
|
861
|
-
async createIndex(collectionName, indexSpec, options) {
|
|
897
|
+
async createIndex(collectionName, indexSpec, options, database, ref, type) {
|
|
862
898
|
this.ensureInitialized();
|
|
863
899
|
try {
|
|
864
|
-
const
|
|
900
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
901
|
+
const collection = db.collection(collectionName);
|
|
865
902
|
const indexName = await collection.createIndex(indexSpec, options);
|
|
866
903
|
return indexName;
|
|
867
904
|
}
|
|
@@ -876,10 +913,11 @@ class SimpleMongoHelper {
|
|
|
876
913
|
* @returns Result object
|
|
877
914
|
* @throws Error if not initialized or if index drop fails
|
|
878
915
|
*/
|
|
879
|
-
async dropIndex(collectionName, indexName) {
|
|
916
|
+
async dropIndex(collectionName, indexName, database, ref, type) {
|
|
880
917
|
this.ensureInitialized();
|
|
881
918
|
try {
|
|
882
|
-
const
|
|
919
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
920
|
+
const collection = db.collection(collectionName);
|
|
883
921
|
const result = await collection.dropIndex(indexName);
|
|
884
922
|
return result;
|
|
885
923
|
}
|
|
@@ -893,10 +931,11 @@ class SimpleMongoHelper {
|
|
|
893
931
|
* @returns Array of index information
|
|
894
932
|
* @throws Error if not initialized or if listing fails
|
|
895
933
|
*/
|
|
896
|
-
async listIndexes(collectionName) {
|
|
934
|
+
async listIndexes(collectionName, database, ref, type) {
|
|
897
935
|
this.ensureInitialized();
|
|
898
936
|
try {
|
|
899
|
-
const
|
|
937
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
938
|
+
const collection = db.collection(collectionName);
|
|
900
939
|
const indexes = await collection.indexes();
|
|
901
940
|
return indexes;
|
|
902
941
|
}
|
|
@@ -958,9 +997,13 @@ class SimpleMongoHelper {
|
|
|
958
997
|
if (!inputConfig) {
|
|
959
998
|
throw new Error(`Ref '${ref}' not found in configuration inputs.`);
|
|
960
999
|
}
|
|
1000
|
+
const database = options?.database;
|
|
1001
|
+
const dbRef = options?.ref;
|
|
1002
|
+
const dbType = options?.type;
|
|
1003
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
|
|
961
1004
|
// Note: loadCollection doesn't support session yet, but we'll pass it through options
|
|
962
1005
|
// For now, we'll use the collection directly with session support
|
|
963
|
-
const collection =
|
|
1006
|
+
const collection = db.collection(inputConfig.collection);
|
|
964
1007
|
const filter = inputConfig.query || {};
|
|
965
1008
|
const session = options?.session;
|
|
966
1009
|
try {
|
|
@@ -1015,8 +1058,12 @@ class SimpleMongoHelper {
|
|
|
1015
1058
|
this.ensureInitialized();
|
|
1016
1059
|
const fieldName = options?.fieldName || '_sig';
|
|
1017
1060
|
const unique = options?.unique !== false; // Default to true
|
|
1061
|
+
const database = options?.database;
|
|
1062
|
+
const ref = options?.ref;
|
|
1063
|
+
const type = options?.type;
|
|
1018
1064
|
try {
|
|
1019
|
-
const
|
|
1065
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1066
|
+
const collection = db.collection(collectionName);
|
|
1020
1067
|
const indexes = await collection.indexes();
|
|
1021
1068
|
// Find existing index on the signature field
|
|
1022
1069
|
const existingIndex = indexes.find(idx => {
|
|
@@ -1086,6 +1133,9 @@ class SimpleMongoHelper {
|
|
|
1086
1133
|
const mode = outputConfig.mode || this.config.output?.mode || 'append';
|
|
1087
1134
|
const ensureIndex = options?.ensureIndex !== false; // Default to true
|
|
1088
1135
|
const session = options?.session;
|
|
1136
|
+
const database = options?.database;
|
|
1137
|
+
const dbRef = options?.ref;
|
|
1138
|
+
const dbType = options?.type;
|
|
1089
1139
|
const result = {
|
|
1090
1140
|
inserted: 0,
|
|
1091
1141
|
updated: 0,
|
|
@@ -1093,7 +1143,8 @@ class SimpleMongoHelper {
|
|
|
1093
1143
|
indexCreated: false,
|
|
1094
1144
|
};
|
|
1095
1145
|
try {
|
|
1096
|
-
const
|
|
1146
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
|
|
1147
|
+
const collection = db.collection(collectionName);
|
|
1097
1148
|
// Handle replace mode: clear collection first
|
|
1098
1149
|
if (mode === 'replace') {
|
|
1099
1150
|
const deleteOptions = session ? { session } : {};
|
|
@@ -1247,6 +1298,9 @@ class SimpleMongoHelper {
|
|
|
1247
1298
|
const writeResult = await this.writeByRef(ref, documents, {
|
|
1248
1299
|
session: options?.session,
|
|
1249
1300
|
ensureIndex: options?.ensureIndex,
|
|
1301
|
+
database: options?.database,
|
|
1302
|
+
ref: options?.ref,
|
|
1303
|
+
type: options?.type,
|
|
1250
1304
|
});
|
|
1251
1305
|
const result = {
|
|
1252
1306
|
...writeResult,
|
|
@@ -1261,7 +1315,7 @@ class SimpleMongoHelper {
|
|
|
1261
1315
|
name: options.complete.name,
|
|
1262
1316
|
provider: options.complete.provider,
|
|
1263
1317
|
metadata: options.complete.metadata,
|
|
1264
|
-
}, { session: options.session });
|
|
1318
|
+
}, { session: options.session, database: options?.database, ref: options?.ref, type: options?.type });
|
|
1265
1319
|
result.completed = true;
|
|
1266
1320
|
}
|
|
1267
1321
|
catch (error) {
|
|
@@ -1282,7 +1336,7 @@ class SimpleMongoHelper {
|
|
|
1282
1336
|
*/
|
|
1283
1337
|
async mergeCollections(options) {
|
|
1284
1338
|
this.ensureInitialized();
|
|
1285
|
-
const { sourceCollection1, sourceCollection2, targetCollection, strategy, key, compositeKeys, joinType, fieldPrefix1 = 'record', fieldPrefix2 = 'assessment', includeIndex = strategy === 'index', onUnmatched1 = 'include', onUnmatched2 = 'include', session, } = options;
|
|
1339
|
+
const { sourceCollection1, sourceCollection2, targetCollection, strategy, key, compositeKeys, joinType, fieldPrefix1 = 'record', fieldPrefix2 = 'assessment', includeIndex = strategy === 'index', onUnmatched1 = 'include', onUnmatched2 = 'include', session, database, ref, type, } = options;
|
|
1286
1340
|
// Determine join behavior from joinType or fallback to onUnmatched flags
|
|
1287
1341
|
let includeUnmatched1;
|
|
1288
1342
|
let includeUnmatched2;
|
|
@@ -1322,9 +1376,10 @@ class SimpleMongoHelper {
|
|
|
1322
1376
|
errors: [],
|
|
1323
1377
|
};
|
|
1324
1378
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
1327
|
-
const
|
|
1379
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1380
|
+
const coll1 = db.collection(sourceCollection1);
|
|
1381
|
+
const coll2 = db.collection(sourceCollection2);
|
|
1382
|
+
const targetColl = db.collection(targetCollection);
|
|
1328
1383
|
const findOptions = {};
|
|
1329
1384
|
if (session) {
|
|
1330
1385
|
findOptions.session = session;
|
|
@@ -1607,14 +1662,84 @@ class SimpleMongoHelper {
|
|
|
1607
1662
|
throw new Error(`Failed to merge collections: ${error instanceof Error ? error.message : String(error)}`);
|
|
1608
1663
|
}
|
|
1609
1664
|
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Registers automatic cleanup handlers for process exit events.
|
|
1667
|
+
* This ensures connections are closed gracefully when the application terminates.
|
|
1668
|
+
* Uses a global registry to handle multiple instances without conflicts.
|
|
1669
|
+
* @internal
|
|
1670
|
+
*/
|
|
1671
|
+
registerCleanup() {
|
|
1672
|
+
if (this.cleanupRegistered) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
this.cleanupRegistered = true;
|
|
1676
|
+
// Global registry for all SimpleMongoHelper instances
|
|
1677
|
+
const globalRegistry = global.__nxMongoHelperInstances || new Set();
|
|
1678
|
+
global.__nxMongoHelperInstances = globalRegistry;
|
|
1679
|
+
globalRegistry.add(this);
|
|
1680
|
+
// Register global cleanup handlers only once
|
|
1681
|
+
if (!global.__nxMongoCleanupRegistered) {
|
|
1682
|
+
global.__nxMongoCleanupRegistered = true;
|
|
1683
|
+
if (typeof process !== 'undefined') {
|
|
1684
|
+
const cleanupAll = async (signal) => {
|
|
1685
|
+
const instances = global.__nxMongoHelperInstances;
|
|
1686
|
+
if (!instances || instances.size === 0) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
// Cleanup all instances in parallel with timeout
|
|
1690
|
+
const cleanupPromises = Array.from(instances).map(async (instance) => {
|
|
1691
|
+
try {
|
|
1692
|
+
// Use the public disconnect method, but with timeout protection
|
|
1693
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Disconnect timeout')), 5000));
|
|
1694
|
+
await Promise.race([
|
|
1695
|
+
instance.disconnect(),
|
|
1696
|
+
timeout
|
|
1697
|
+
]).catch(() => {
|
|
1698
|
+
// Ignore timeout errors during automatic cleanup
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
catch (error) {
|
|
1702
|
+
// Silently ignore errors during automatic cleanup
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
await Promise.all(cleanupPromises).catch(() => {
|
|
1706
|
+
// Ignore errors during cleanup
|
|
1707
|
+
});
|
|
1708
|
+
};
|
|
1709
|
+
// Graceful shutdown signals
|
|
1710
|
+
process.once('SIGINT', () => {
|
|
1711
|
+
cleanupAll('SIGINT').finally(() => {
|
|
1712
|
+
process.exit(0);
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
1715
|
+
process.once('SIGTERM', () => {
|
|
1716
|
+
cleanupAll('SIGTERM').finally(() => {
|
|
1717
|
+
process.exit(0);
|
|
1718
|
+
});
|
|
1719
|
+
});
|
|
1720
|
+
// Before exit (allows async cleanup)
|
|
1721
|
+
process.once('beforeExit', () => {
|
|
1722
|
+
cleanupAll('beforeExit');
|
|
1723
|
+
});
|
|
1724
|
+
// Note: We don't handle uncaughtException/unhandledRejection here
|
|
1725
|
+
// as those should be handled by the application, not the library
|
|
1726
|
+
// The beforeExit handler will still clean up connections
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1610
1730
|
/**
|
|
1611
1731
|
* Closes the MongoDB connection and cleans up resources.
|
|
1612
|
-
*
|
|
1732
|
+
* This method is called automatically on process exit, but can also be called manually.
|
|
1733
|
+
* @throws Error if disconnect fails (only when called manually, not during automatic cleanup)
|
|
1613
1734
|
*/
|
|
1614
1735
|
async disconnect() {
|
|
1736
|
+
if (this.isDisconnecting) {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1615
1739
|
if (!this.isInitialized || !this.client) {
|
|
1616
1740
|
return;
|
|
1617
1741
|
}
|
|
1742
|
+
this.isDisconnecting = true;
|
|
1618
1743
|
try {
|
|
1619
1744
|
await this.client.close();
|
|
1620
1745
|
this.client = null;
|
|
@@ -1622,8 +1747,12 @@ class SimpleMongoHelper {
|
|
|
1622
1747
|
this.isInitialized = false;
|
|
1623
1748
|
}
|
|
1624
1749
|
catch (error) {
|
|
1750
|
+
this.isDisconnecting = false;
|
|
1625
1751
|
throw new Error(`Failed to disconnect: ${error instanceof Error ? error.message : String(error)}`);
|
|
1626
1752
|
}
|
|
1753
|
+
finally {
|
|
1754
|
+
this.isDisconnecting = false;
|
|
1755
|
+
}
|
|
1627
1756
|
}
|
|
1628
1757
|
/**
|
|
1629
1758
|
* Ensures the helper is initialized before performing operations.
|
|
@@ -1646,22 +1775,111 @@ class SimpleMongoHelper {
|
|
|
1646
1775
|
return this.db;
|
|
1647
1776
|
}
|
|
1648
1777
|
/**
|
|
1649
|
-
*
|
|
1650
|
-
* @param connectionString - MongoDB connection string
|
|
1651
|
-
* @returns
|
|
1778
|
+
* Strips database name from MongoDB connection string, returning base connection string.
|
|
1779
|
+
* @param connectionString - MongoDB connection string (may include database name)
|
|
1780
|
+
* @returns Base connection string without database name
|
|
1781
|
+
* @example
|
|
1782
|
+
* stripDatabaseFromConnectionString('mongodb://localhost:27017/admin')
|
|
1783
|
+
* // Returns: 'mongodb://localhost:27017/'
|
|
1652
1784
|
*/
|
|
1653
|
-
|
|
1785
|
+
stripDatabaseFromConnectionString(connectionString) {
|
|
1654
1786
|
try {
|
|
1655
1787
|
const url = new URL(connectionString);
|
|
1656
|
-
|
|
1657
|
-
|
|
1788
|
+
// Remove pathname (database name) but keep trailing slash
|
|
1789
|
+
url.pathname = '/';
|
|
1790
|
+
return url.toString();
|
|
1658
1791
|
}
|
|
1659
1792
|
catch {
|
|
1660
|
-
// If URL parsing fails, try
|
|
1661
|
-
|
|
1662
|
-
|
|
1793
|
+
// If URL parsing fails, try regex pattern matching
|
|
1794
|
+
// Match: mongodb://host:port/database?options or mongodb://host:port/database
|
|
1795
|
+
const match = connectionString.match(/^([^\/]+\/\/[^\/]+)\/([^\/\?]+)(\?.*)?$/);
|
|
1796
|
+
if (match) {
|
|
1797
|
+
// Return base URL with trailing slash
|
|
1798
|
+
return match[1] + '/';
|
|
1799
|
+
}
|
|
1800
|
+
// If no database found, return as-is (should already be base URL)
|
|
1801
|
+
return connectionString.endsWith('/') ? connectionString : connectionString + '/';
|
|
1663
1802
|
}
|
|
1664
1803
|
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Resolves the database name from provided options using the databases config map.
|
|
1806
|
+
* Priority: database > ref+type > ref > type
|
|
1807
|
+
* @param options - Options containing database, ref, and/or type
|
|
1808
|
+
* @returns Resolved database name or undefined (will default to 'admin')
|
|
1809
|
+
* @throws Error if no match found or multiple matches found
|
|
1810
|
+
* @internal
|
|
1811
|
+
*/
|
|
1812
|
+
resolveDatabase(options) {
|
|
1813
|
+
// Priority 1: If database is provided directly, use it
|
|
1814
|
+
if (options?.database) {
|
|
1815
|
+
return options.database;
|
|
1816
|
+
}
|
|
1817
|
+
// If no config or no databases map, return undefined (will default to 'admin')
|
|
1818
|
+
if (!this.config || !this.config.databases || this.config.databases.length === 0) {
|
|
1819
|
+
return undefined;
|
|
1820
|
+
}
|
|
1821
|
+
const databases = this.config.databases;
|
|
1822
|
+
// Priority 2: If both ref and type are provided, find exact match
|
|
1823
|
+
if (options?.ref && options?.type) {
|
|
1824
|
+
const matches = databases.filter(db => db.ref === options.ref && db.type === options.type);
|
|
1825
|
+
if (matches.length === 0) {
|
|
1826
|
+
throw new Error(`No database found for ref: ${options.ref} and type: ${options.type}`);
|
|
1827
|
+
}
|
|
1828
|
+
if (matches.length > 1) {
|
|
1829
|
+
throw new Error(`Multiple databases found for ref: ${options.ref} and type: ${options.type}`);
|
|
1830
|
+
}
|
|
1831
|
+
return matches[0].database;
|
|
1832
|
+
}
|
|
1833
|
+
// Priority 3: If ref is provided, find by ref
|
|
1834
|
+
if (options?.ref) {
|
|
1835
|
+
const matches = databases.filter(db => db.ref === options.ref);
|
|
1836
|
+
if (matches.length === 0) {
|
|
1837
|
+
throw new Error(`No database found for ref: ${options.ref}`);
|
|
1838
|
+
}
|
|
1839
|
+
if (matches.length > 1) {
|
|
1840
|
+
throw new Error(`Multiple databases found for ref: ${options.ref}. Use 'type' parameter to narrow down.`);
|
|
1841
|
+
}
|
|
1842
|
+
return matches[0].database;
|
|
1843
|
+
}
|
|
1844
|
+
// Priority 4: If type is provided, find by type
|
|
1845
|
+
if (options?.type) {
|
|
1846
|
+
const matches = databases.filter(db => db.type === options.type);
|
|
1847
|
+
if (matches.length === 0) {
|
|
1848
|
+
throw new Error(`No database found for type: ${options.type}`);
|
|
1849
|
+
}
|
|
1850
|
+
if (matches.length > 1) {
|
|
1851
|
+
throw new Error(`Multiple databases found for type: ${options.type}. Use 'ref' parameter to narrow down.`);
|
|
1852
|
+
}
|
|
1853
|
+
return matches[0].database;
|
|
1854
|
+
}
|
|
1855
|
+
// No options provided, return undefined (will default to 'admin')
|
|
1856
|
+
return undefined;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Gets a database instance by name. Defaults to 'admin' if no name provided.
|
|
1860
|
+
* @param databaseName - Optional database name (defaults to 'admin')
|
|
1861
|
+
* @returns Database instance
|
|
1862
|
+
* @throws Error if client is not initialized
|
|
1863
|
+
* @internal
|
|
1864
|
+
*/
|
|
1865
|
+
getDatabase(databaseName) {
|
|
1866
|
+
if (!this.client) {
|
|
1867
|
+
throw new Error('MongoDB client not initialized. Call initialize() first.');
|
|
1868
|
+
}
|
|
1869
|
+
return this.client.db(databaseName || 'admin');
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Gets a database instance by name. Defaults to 'admin' if no name provided.
|
|
1873
|
+
* Public method for use by ProgressAPIImpl.
|
|
1874
|
+
* @param databaseName - Optional database name (defaults to 'admin')
|
|
1875
|
+
* @param ref - Optional ref for database resolution
|
|
1876
|
+
* @param type - Optional type for database resolution
|
|
1877
|
+
* @returns Database instance
|
|
1878
|
+
* @throws Error if client is not initialized
|
|
1879
|
+
*/
|
|
1880
|
+
getDatabaseByName(databaseName, ref, type) {
|
|
1881
|
+
return this.getDatabase(this.resolveDatabase({ database: databaseName, ref, type }));
|
|
1882
|
+
}
|
|
1665
1883
|
}
|
|
1666
1884
|
exports.SimpleMongoHelper = SimpleMongoHelper;
|
|
1667
1885
|
//# sourceMappingURL=simpleMongoHelper.js.map
|