nx-mongo 3.6.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.
@@ -47,6 +47,11 @@ export interface HelperConfig {
47
47
  uniqueIndexKeys?: string[];
48
48
  provider?: string;
49
49
  };
50
+ databases?: Array<{
51
+ ref: string;
52
+ type: string;
53
+ database: string;
54
+ }>;
50
55
  }
51
56
 
52
57
  export interface WriteByRefResult {
@@ -82,7 +87,7 @@ export interface StageRecord extends StageIdentity {
82
87
  metadata?: StageMetadata;
83
88
  }
84
89
 
85
- export type ProgressSessionOptions = { session?: ClientSession };
90
+ export type ProgressSessionOptions = { session?: ClientSession; database?: string; ref?: string; type?: string };
86
91
 
87
92
  export interface ProgressAPI {
88
93
  isCompleted(key: string, options?: ProgressSessionOptions & { process?: string; provider?: string }): Promise<boolean>;
@@ -99,6 +104,9 @@ export interface ProgressAPI {
99
104
  export interface WriteStageOptions {
100
105
  ensureIndex?: boolean;
101
106
  session?: ClientSession;
107
+ database?: string;
108
+ ref?: string;
109
+ type?: string;
102
110
  complete?: {
103
111
  key: string;
104
112
  process?: string;
@@ -126,6 +134,9 @@ export interface MergeCollectionsOptions {
126
134
  onUnmatched1?: 'include' | 'skip'; // What to do with unmatched records from collection 1 (default: 'include', deprecated: use joinType instead)
127
135
  onUnmatched2?: 'include' | 'skip'; // What to do with unmatched records from collection 2 (default: 'include', deprecated: use joinType instead)
128
136
  session?: ClientSession;
137
+ database?: string; // Database name (defaults to 'admin')
138
+ ref?: string; // Optional ref for database resolution
139
+ type?: string; // Optional type for database resolution
129
140
  }
130
141
 
131
142
  export interface MergeCollectionsResult {
@@ -361,7 +372,7 @@ class ProgressAPIImpl implements ProgressAPI {
361
372
  * Ensures the progress collection and unique index exist.
362
373
  * Called lazily on first use.
363
374
  */
364
- private async ensureProgressIndex(session?: ClientSession): Promise<void> {
375
+ private async ensureProgressIndex(session?: ClientSession, database?: string, ref?: string, type?: string): Promise<void> {
365
376
  if (this.indexEnsured) {
366
377
  return;
367
378
  }
@@ -369,7 +380,8 @@ class ProgressAPIImpl implements ProgressAPI {
369
380
  this.helper.ensureInitialized();
370
381
 
371
382
  try {
372
- const collection = this.helper.getDb().collection(this.config.collection);
383
+ const db = this.helper.getDatabaseByName(database, ref, type);
384
+ const collection = db.collection(this.config.collection);
373
385
  const indexes = await collection.indexes();
374
386
 
375
387
  // Build index spec from uniqueIndexKeys
@@ -429,13 +441,17 @@ class ProgressAPIImpl implements ProgressAPI {
429
441
  }
430
442
 
431
443
  async isCompleted(key: string, options?: ProgressSessionOptions & { process?: string; provider?: string }): Promise<boolean> {
432
- await this.ensureProgressIndex(options?.session);
444
+ const database = options?.database;
445
+ const ref = options?.ref;
446
+ const type = options?.type;
447
+ await this.ensureProgressIndex(options?.session, database, ref, type);
433
448
  this.helper.ensureInitialized();
434
449
 
435
450
  const provider = this.resolveProvider(options);
436
451
  const process = options?.process;
437
452
  const filter = this.buildFilter(key, process, provider);
438
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
453
+ const db = this.helper.getDatabaseByName(database, ref, type);
454
+ const collection = db.collection<StageRecord>(this.config.collection);
439
455
 
440
456
  const findOptions: any = {};
441
457
  if (options?.session) {
@@ -447,13 +463,17 @@ class ProgressAPIImpl implements ProgressAPI {
447
463
  }
448
464
 
449
465
  async start(identity: StageIdentity, options?: ProgressSessionOptions): Promise<void> {
450
- await this.ensureProgressIndex(options?.session);
466
+ const database = options?.database;
467
+ const ref = options?.ref;
468
+ const type = options?.type;
469
+ await this.ensureProgressIndex(options?.session, database, ref, type);
451
470
  this.helper.ensureInitialized();
452
471
 
453
472
  const provider = this.resolveProvider({ provider: identity.provider });
454
473
  const process = identity.process;
455
474
  const filter = this.buildFilter(identity.key, process, provider);
456
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
475
+ const db = this.helper.getDatabaseByName(database, ref, type);
476
+ const collection = db.collection<StageRecord>(this.config.collection);
457
477
 
458
478
  const update: UpdateFilter<StageRecord> = {
459
479
  $set: {
@@ -485,13 +505,17 @@ class ProgressAPIImpl implements ProgressAPI {
485
505
  identity: StageIdentity & { metadata?: StageMetadata },
486
506
  options?: ProgressSessionOptions
487
507
  ): Promise<void> {
488
- await this.ensureProgressIndex(options?.session);
508
+ const database = options?.database;
509
+ const ref = options?.ref;
510
+ const type = options?.type;
511
+ await this.ensureProgressIndex(options?.session, database, ref, type);
489
512
  this.helper.ensureInitialized();
490
513
 
491
514
  const provider = this.resolveProvider({ provider: identity.provider });
492
515
  const process = identity.process;
493
516
  const filter = this.buildFilter(identity.key, process, provider);
494
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
517
+ const db = this.helper.getDatabaseByName(database, ref, type);
518
+ const collection = db.collection<StageRecord>(this.config.collection);
495
519
 
496
520
  const update: UpdateFilter<StageRecord> = {
497
521
  $set: {
@@ -526,7 +550,10 @@ class ProgressAPIImpl implements ProgressAPI {
526
550
  }
527
551
 
528
552
  async getCompleted(options?: ProgressSessionOptions & { process?: string; provider?: string }): Promise<Array<Pick<StageRecord, 'key' | 'name' | 'completedAt'>>> {
529
- await this.ensureProgressIndex(options?.session);
553
+ const database = options?.database;
554
+ const ref = options?.ref;
555
+ const type = options?.type;
556
+ await this.ensureProgressIndex(options?.session, database, ref, type);
530
557
  this.helper.ensureInitialized();
531
558
 
532
559
  const provider = this.resolveProvider(options);
@@ -539,7 +566,8 @@ class ProgressAPIImpl implements ProgressAPI {
539
566
  filter.provider = provider;
540
567
  }
541
568
 
542
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
569
+ const db = this.helper.getDatabaseByName(database, ref, type);
570
+ const collection = db.collection<StageRecord>(this.config.collection);
543
571
  const findOptions: any = { projection: { key: 1, name: 1, completedAt: 1 } };
544
572
  if (options?.session) {
545
573
  findOptions.session = options.session;
@@ -554,7 +582,10 @@ class ProgressAPIImpl implements ProgressAPI {
554
582
  }
555
583
 
556
584
  async getProgress(options?: ProgressSessionOptions & { process?: string; provider?: string }): Promise<StageRecord[]> {
557
- await this.ensureProgressIndex(options?.session);
585
+ const database = options?.database;
586
+ const ref = options?.ref;
587
+ const type = options?.type;
588
+ await this.ensureProgressIndex(options?.session, database, ref, type);
558
589
  this.helper.ensureInitialized();
559
590
 
560
591
  const provider = this.resolveProvider(options);
@@ -567,7 +598,8 @@ class ProgressAPIImpl implements ProgressAPI {
567
598
  filter.provider = provider;
568
599
  }
569
600
 
570
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
601
+ const db = this.helper.getDatabaseByName(database, ref, type);
602
+ const collection = db.collection<StageRecord>(this.config.collection);
571
603
  const findOptions: any = {};
572
604
  if (options?.session) {
573
605
  findOptions.session = options.session;
@@ -577,13 +609,17 @@ class ProgressAPIImpl implements ProgressAPI {
577
609
  }
578
610
 
579
611
  async reset(key: string, options?: ProgressSessionOptions & { process?: string; provider?: string }): Promise<void> {
580
- await this.ensureProgressIndex(options?.session);
612
+ const database = options?.database;
613
+ const ref = options?.ref;
614
+ const type = options?.type;
615
+ await this.ensureProgressIndex(options?.session, database, ref, type);
581
616
  this.helper.ensureInitialized();
582
617
 
583
618
  const provider = this.resolveProvider(options);
584
619
  const process = options?.process;
585
620
  const filter = this.buildFilter(key, process, provider);
586
- const collection = this.helper.getDb().collection<StageRecord>(this.config.collection);
621
+ const db = this.helper.getDatabaseByName(database, ref, type);
622
+ const collection = db.collection<StageRecord>(this.config.collection);
587
623
 
588
624
  const update: UpdateFilter<StageRecord> = {
589
625
  $set: {
@@ -616,7 +652,8 @@ export class SimpleMongoHelper {
616
652
  private isDisconnecting: boolean = false;
617
653
 
618
654
  constructor(connectionString: string, retryOptions?: RetryOptions, config?: HelperConfig) {
619
- this.connectionString = connectionString;
655
+ // Strip database name from connection string if present
656
+ this.connectionString = this.stripDatabaseFromConnectionString(connectionString);
620
657
  this.retryOptions = {
621
658
  maxRetries: retryOptions?.maxRetries ?? 3,
622
659
  retryDelay: retryOptions?.retryDelay ?? 1000,
@@ -733,9 +770,8 @@ export class SimpleMongoHelper {
733
770
 
734
771
  await testClient.connect();
735
772
 
736
- // Try to ping the server
737
- const dbName = this.extractDatabaseName(this.connectionString);
738
- const testDb = testClient.db(dbName);
773
+ // Try to ping the server using 'admin' database (default)
774
+ const testDb = testClient.db('admin');
739
775
  await testDb.admin().ping();
740
776
 
741
777
  // Test basic operation (list collections)
@@ -864,9 +900,8 @@ export class SimpleMongoHelper {
864
900
  try {
865
901
  this.client = new MongoClient(this.connectionString);
866
902
  await this.client.connect();
867
- // Extract database name from connection string or use default
868
- const dbName = this.extractDatabaseName(this.connectionString);
869
- this.db = this.client.db(dbName);
903
+ // Default to 'admin' database for initial connection (not used for operations)
904
+ this.db = this.client.db('admin');
870
905
  this.isInitialized = true;
871
906
  return;
872
907
  } catch (error) {
@@ -897,12 +932,16 @@ export class SimpleMongoHelper {
897
932
  async loadCollection<T extends Document = Document>(
898
933
  collectionName: string,
899
934
  query?: Filter<T>,
900
- options?: PaginationOptions
935
+ options?: PaginationOptions,
936
+ database?: string,
937
+ ref?: string,
938
+ type?: string
901
939
  ): Promise<WithId<T>[] | PaginatedResult<T>> {
902
940
  this.ensureInitialized();
903
941
 
904
942
  try {
905
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
943
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
944
+ const collection: Collection<T> = db.collection<T>(collectionName);
906
945
  const filter = query || {};
907
946
  let cursor = collection.find(filter);
908
947
 
@@ -954,12 +993,16 @@ export class SimpleMongoHelper {
954
993
  async findOne<T extends Document = Document>(
955
994
  collectionName: string,
956
995
  query: Filter<T>,
957
- options?: { sort?: Sort; projection?: Document }
996
+ options?: { sort?: Sort; projection?: Document },
997
+ database?: string,
998
+ ref?: string,
999
+ type?: string
958
1000
  ): Promise<WithId<T> | null> {
959
1001
  this.ensureInitialized();
960
1002
 
961
1003
  try {
962
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1004
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1005
+ const collection: Collection<T> = db.collection<T>(collectionName);
963
1006
  let findOptions: any = {};
964
1007
 
965
1008
  if (options?.sort) {
@@ -987,12 +1030,16 @@ export class SimpleMongoHelper {
987
1030
  async insert<T extends Document = Document>(
988
1031
  collectionName: string,
989
1032
  data: OptionalUnlessRequiredId<T> | OptionalUnlessRequiredId<T>[],
990
- options?: { session?: ClientSession }
1033
+ options?: { session?: ClientSession },
1034
+ database?: string,
1035
+ ref?: string,
1036
+ type?: string
991
1037
  ): Promise<any> {
992
1038
  this.ensureInitialized();
993
1039
 
994
1040
  try {
995
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1041
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1042
+ const collection: Collection<T> = db.collection<T>(collectionName);
996
1043
  const insertOptions = options?.session ? { session: options.session } : {};
997
1044
 
998
1045
  if (Array.isArray(data)) {
@@ -1020,12 +1067,16 @@ export class SimpleMongoHelper {
1020
1067
  collectionName: string,
1021
1068
  filter: Filter<T>,
1022
1069
  updateData: UpdateFilter<T>,
1023
- options?: { upsert?: boolean; multi?: boolean; session?: ClientSession }
1070
+ options?: { upsert?: boolean; multi?: boolean; session?: ClientSession },
1071
+ database?: string,
1072
+ ref?: string,
1073
+ type?: string
1024
1074
  ): Promise<any> {
1025
1075
  this.ensureInitialized();
1026
1076
 
1027
1077
  try {
1028
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1078
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1079
+ const collection: Collection<T> = db.collection<T>(collectionName);
1029
1080
  const updateOptions: any = {};
1030
1081
  if (options?.upsert !== undefined) updateOptions.upsert = options.upsert;
1031
1082
  if (options?.session) updateOptions.session = options.session;
@@ -1053,12 +1104,16 @@ export class SimpleMongoHelper {
1053
1104
  async delete<T extends Document = Document>(
1054
1105
  collectionName: string,
1055
1106
  filter: Filter<T>,
1056
- options?: { multi?: boolean }
1107
+ options?: { multi?: boolean },
1108
+ database?: string,
1109
+ ref?: string,
1110
+ type?: string
1057
1111
  ): Promise<any> {
1058
1112
  this.ensureInitialized();
1059
1113
 
1060
1114
  try {
1061
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1115
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1116
+ const collection: Collection<T> = db.collection<T>(collectionName);
1062
1117
 
1063
1118
  if (options?.multi) {
1064
1119
  const result = await collection.deleteMany(filter);
@@ -1081,12 +1136,16 @@ export class SimpleMongoHelper {
1081
1136
  */
1082
1137
  async countDocuments<T extends Document = Document>(
1083
1138
  collectionName: string,
1084
- query?: Filter<T>
1139
+ query?: Filter<T>,
1140
+ database?: string,
1141
+ ref?: string,
1142
+ type?: string
1085
1143
  ): Promise<number> {
1086
1144
  this.ensureInitialized();
1087
1145
 
1088
1146
  try {
1089
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1147
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1148
+ const collection: Collection<T> = db.collection<T>(collectionName);
1090
1149
  const count = await collection.countDocuments(query || {});
1091
1150
  return count;
1092
1151
  } catch (error) {
@@ -1100,11 +1159,12 @@ export class SimpleMongoHelper {
1100
1159
  * @returns Estimated number of documents
1101
1160
  * @throws Error if not initialized or if count fails
1102
1161
  */
1103
- async estimatedDocumentCount(collectionName: string): Promise<number> {
1162
+ async estimatedDocumentCount(collectionName: string, database?: string, ref?: string, type?: string): Promise<number> {
1104
1163
  this.ensureInitialized();
1105
1164
 
1106
1165
  try {
1107
- const collection = this.db!.collection(collectionName);
1166
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1167
+ const collection = db.collection(collectionName);
1108
1168
  const count = await collection.estimatedDocumentCount();
1109
1169
  return count;
1110
1170
  } catch (error) {
@@ -1121,12 +1181,16 @@ export class SimpleMongoHelper {
1121
1181
  */
1122
1182
  async aggregate<T extends Document = Document>(
1123
1183
  collectionName: string,
1124
- pipeline: Document[]
1184
+ pipeline: Document[],
1185
+ database?: string,
1186
+ ref?: string,
1187
+ type?: string
1125
1188
  ): Promise<T[]> {
1126
1189
  this.ensureInitialized();
1127
1190
 
1128
1191
  try {
1129
- const collection: Collection<T> = this.db!.collection<T>(collectionName);
1192
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1193
+ const collection: Collection<T> = db.collection<T>(collectionName);
1130
1194
  const results = await collection.aggregate<T>(pipeline).toArray();
1131
1195
  return results;
1132
1196
  } catch (error) {
@@ -1145,12 +1209,16 @@ export class SimpleMongoHelper {
1145
1209
  async createIndex(
1146
1210
  collectionName: string,
1147
1211
  indexSpec: IndexSpecification,
1148
- options?: CreateIndexesOptions
1212
+ options?: CreateIndexesOptions,
1213
+ database?: string,
1214
+ ref?: string,
1215
+ type?: string
1149
1216
  ): Promise<string> {
1150
1217
  this.ensureInitialized();
1151
1218
 
1152
1219
  try {
1153
- const collection = this.db!.collection(collectionName);
1220
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1221
+ const collection = db.collection(collectionName);
1154
1222
  const indexName = await collection.createIndex(indexSpec, options);
1155
1223
  return indexName;
1156
1224
  } catch (error) {
@@ -1165,11 +1233,12 @@ export class SimpleMongoHelper {
1165
1233
  * @returns Result object
1166
1234
  * @throws Error if not initialized or if index drop fails
1167
1235
  */
1168
- async dropIndex(collectionName: string, indexName: string): Promise<any> {
1236
+ async dropIndex(collectionName: string, indexName: string, database?: string, ref?: string, type?: string): Promise<any> {
1169
1237
  this.ensureInitialized();
1170
1238
 
1171
1239
  try {
1172
- const collection = this.db!.collection(collectionName);
1240
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1241
+ const collection = db.collection(collectionName);
1173
1242
  const result = await collection.dropIndex(indexName);
1174
1243
  return result;
1175
1244
  } catch (error) {
@@ -1183,11 +1252,12 @@ export class SimpleMongoHelper {
1183
1252
  * @returns Array of index information
1184
1253
  * @throws Error if not initialized or if listing fails
1185
1254
  */
1186
- async listIndexes(collectionName: string): Promise<Document[]> {
1255
+ async listIndexes(collectionName: string, database?: string, ref?: string, type?: string): Promise<Document[]> {
1187
1256
  this.ensureInitialized();
1188
1257
 
1189
1258
  try {
1190
- const collection = this.db!.collection(collectionName);
1259
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1260
+ const collection = db.collection(collectionName);
1191
1261
  const indexes = await collection.indexes();
1192
1262
  return indexes;
1193
1263
  } catch (error) {
@@ -1249,7 +1319,7 @@ export class SimpleMongoHelper {
1249
1319
  */
1250
1320
  async loadByRef<T extends Document = Document>(
1251
1321
  ref: string,
1252
- options?: PaginationOptions & { session?: ClientSession }
1322
+ options?: PaginationOptions & { session?: ClientSession; database?: string; ref?: string; type?: string }
1253
1323
  ): Promise<WithId<T>[] | PaginatedResult<T>> {
1254
1324
  this.ensureInitialized();
1255
1325
 
@@ -1262,9 +1332,13 @@ export class SimpleMongoHelper {
1262
1332
  throw new Error(`Ref '${ref}' not found in configuration inputs.`);
1263
1333
  }
1264
1334
 
1335
+ const database = options?.database;
1336
+ const dbRef = options?.ref;
1337
+ const dbType = options?.type;
1338
+ const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
1265
1339
  // Note: loadCollection doesn't support session yet, but we'll pass it through options
1266
1340
  // For now, we'll use the collection directly with session support
1267
- const collection: Collection<T> = this.db!.collection<T>(inputConfig.collection);
1341
+ const collection: Collection<T> = db.collection<T>(inputConfig.collection);
1268
1342
  const filter = inputConfig.query || {};
1269
1343
  const session = options?.session;
1270
1344
 
@@ -1328,15 +1402,22 @@ export class SimpleMongoHelper {
1328
1402
  options?: {
1329
1403
  fieldName?: string;
1330
1404
  unique?: boolean;
1405
+ database?: string;
1406
+ ref?: string;
1407
+ type?: string;
1331
1408
  }
1332
1409
  ): Promise<EnsureSignatureIndexResult> {
1333
1410
  this.ensureInitialized();
1334
1411
 
1335
1412
  const fieldName = options?.fieldName || '_sig';
1336
1413
  const unique = options?.unique !== false; // Default to true
1414
+ const database = options?.database;
1415
+ const ref = options?.ref;
1416
+ const type = options?.type;
1337
1417
 
1338
1418
  try {
1339
- const collection = this.db!.collection(collectionName);
1419
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1420
+ const collection = db.collection(collectionName);
1340
1421
  const indexes = await collection.indexes();
1341
1422
 
1342
1423
  // Find existing index on the signature field
@@ -1402,6 +1483,9 @@ export class SimpleMongoHelper {
1402
1483
  options?: {
1403
1484
  session?: ClientSession;
1404
1485
  ensureIndex?: boolean;
1486
+ database?: string;
1487
+ ref?: string;
1488
+ type?: string;
1405
1489
  }
1406
1490
  ): Promise<WriteByRefResult> {
1407
1491
  this.ensureInitialized();
@@ -1420,6 +1504,9 @@ export class SimpleMongoHelper {
1420
1504
  const mode = outputConfig.mode || this.config.output?.mode || 'append';
1421
1505
  const ensureIndex = options?.ensureIndex !== false; // Default to true
1422
1506
  const session = options?.session;
1507
+ const database = options?.database;
1508
+ const dbRef = options?.ref;
1509
+ const dbType = options?.type;
1423
1510
 
1424
1511
  const result: WriteByRefResult = {
1425
1512
  inserted: 0,
@@ -1429,7 +1516,8 @@ export class SimpleMongoHelper {
1429
1516
  };
1430
1517
 
1431
1518
  try {
1432
- const collection = this.db!.collection(collectionName);
1519
+ const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
1520
+ const collection = db.collection(collectionName);
1433
1521
 
1434
1522
  // Handle replace mode: clear collection first
1435
1523
  if (mode === 'replace') {
@@ -1595,6 +1683,9 @@ export class SimpleMongoHelper {
1595
1683
  const writeResult = await this.writeByRef(ref, documents, {
1596
1684
  session: options?.session,
1597
1685
  ensureIndex: options?.ensureIndex,
1686
+ database: options?.database,
1687
+ ref: options?.ref,
1688
+ type: options?.type,
1598
1689
  });
1599
1690
 
1600
1691
  const result: WriteStageResult = {
@@ -1613,7 +1704,7 @@ export class SimpleMongoHelper {
1613
1704
  provider: options.complete.provider,
1614
1705
  metadata: options.complete.metadata,
1615
1706
  },
1616
- { session: options.session }
1707
+ { session: options.session, database: options?.database, ref: options?.ref, type: options?.type }
1617
1708
  );
1618
1709
  result.completed = true;
1619
1710
  } catch (error) {
@@ -1651,6 +1742,9 @@ export class SimpleMongoHelper {
1651
1742
  onUnmatched1 = 'include',
1652
1743
  onUnmatched2 = 'include',
1653
1744
  session,
1745
+ database,
1746
+ ref,
1747
+ type,
1654
1748
  } = options;
1655
1749
 
1656
1750
  // Determine join behavior from joinType or fallback to onUnmatched flags
@@ -1694,9 +1788,10 @@ export class SimpleMongoHelper {
1694
1788
  };
1695
1789
 
1696
1790
  try {
1697
- const coll1 = this.db!.collection(sourceCollection1);
1698
- const coll2 = this.db!.collection(sourceCollection2);
1699
- const targetColl = this.db!.collection(targetCollection);
1791
+ const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
1792
+ const coll1 = db.collection(sourceCollection1);
1793
+ const coll2 = db.collection(sourceCollection2);
1794
+ const targetColl = db.collection(targetCollection);
1700
1795
 
1701
1796
  const findOptions: any = {};
1702
1797
  if (session) {
@@ -2116,20 +2211,118 @@ export class SimpleMongoHelper {
2116
2211
  }
2117
2212
 
2118
2213
  /**
2119
- * Extracts database name from MongoDB connection string.
2120
- * @param connectionString - MongoDB connection string
2121
- * @returns Database name or 'test' as default
2214
+ * Strips database name from MongoDB connection string, returning base connection string.
2215
+ * @param connectionString - MongoDB connection string (may include database name)
2216
+ * @returns Base connection string without database name
2217
+ * @example
2218
+ * stripDatabaseFromConnectionString('mongodb://localhost:27017/admin')
2219
+ * // Returns: 'mongodb://localhost:27017/'
2122
2220
  */
2123
- private extractDatabaseName(connectionString: string): string {
2221
+ private stripDatabaseFromConnectionString(connectionString: string): string {
2124
2222
  try {
2125
2223
  const url = new URL(connectionString);
2126
- const dbName = url.pathname.slice(1); // Remove leading '/'
2127
- return dbName || 'test';
2224
+ // Remove pathname (database name) but keep trailing slash
2225
+ url.pathname = '/';
2226
+ return url.toString();
2128
2227
  } catch {
2129
- // If URL parsing fails, try to extract from connection string pattern
2130
- const match = connectionString.match(/\/([^\/\?]+)(\?|$)/);
2131
- return match ? match[1] : 'test';
2228
+ // If URL parsing fails, try regex pattern matching
2229
+ // Match: mongodb://host:port/database?options or mongodb://host:port/database
2230
+ const match = connectionString.match(/^([^\/]+\/\/[^\/]+)\/([^\/\?]+)(\?.*)?$/);
2231
+ if (match) {
2232
+ // Return base URL with trailing slash
2233
+ return match[1] + '/';
2234
+ }
2235
+ // If no database found, return as-is (should already be base URL)
2236
+ return connectionString.endsWith('/') ? connectionString : connectionString + '/';
2237
+ }
2238
+ }
2239
+
2240
+ /**
2241
+ * Resolves the database name from provided options using the databases config map.
2242
+ * Priority: database > ref+type > ref > type
2243
+ * @param options - Options containing database, ref, and/or type
2244
+ * @returns Resolved database name or undefined (will default to 'admin')
2245
+ * @throws Error if no match found or multiple matches found
2246
+ * @internal
2247
+ */
2248
+ private resolveDatabase(options?: { database?: string; ref?: string; type?: string }): string | undefined {
2249
+ // Priority 1: If database is provided directly, use it
2250
+ if (options?.database) {
2251
+ return options.database;
2252
+ }
2253
+
2254
+ // If no config or no databases map, return undefined (will default to 'admin')
2255
+ if (!this.config || !this.config.databases || this.config.databases.length === 0) {
2256
+ return undefined;
2132
2257
  }
2258
+
2259
+ const databases = this.config.databases;
2260
+
2261
+ // Priority 2: If both ref and type are provided, find exact match
2262
+ if (options?.ref && options?.type) {
2263
+ const matches = databases.filter(db => db.ref === options.ref && db.type === options.type);
2264
+ if (matches.length === 0) {
2265
+ throw new Error(`No database found for ref: ${options.ref} and type: ${options.type}`);
2266
+ }
2267
+ if (matches.length > 1) {
2268
+ throw new Error(`Multiple databases found for ref: ${options.ref} and type: ${options.type}`);
2269
+ }
2270
+ return matches[0].database;
2271
+ }
2272
+
2273
+ // Priority 3: If ref is provided, find by ref
2274
+ if (options?.ref) {
2275
+ const matches = databases.filter(db => db.ref === options.ref);
2276
+ if (matches.length === 0) {
2277
+ throw new Error(`No database found for ref: ${options.ref}`);
2278
+ }
2279
+ if (matches.length > 1) {
2280
+ throw new Error(`Multiple databases found for ref: ${options.ref}. Use 'type' parameter to narrow down.`);
2281
+ }
2282
+ return matches[0].database;
2283
+ }
2284
+
2285
+ // Priority 4: If type is provided, find by type
2286
+ if (options?.type) {
2287
+ const matches = databases.filter(db => db.type === options.type);
2288
+ if (matches.length === 0) {
2289
+ throw new Error(`No database found for type: ${options.type}`);
2290
+ }
2291
+ if (matches.length > 1) {
2292
+ throw new Error(`Multiple databases found for type: ${options.type}. Use 'ref' parameter to narrow down.`);
2293
+ }
2294
+ return matches[0].database;
2295
+ }
2296
+
2297
+ // No options provided, return undefined (will default to 'admin')
2298
+ return undefined;
2299
+ }
2300
+
2301
+ /**
2302
+ * Gets a database instance by name. Defaults to 'admin' if no name provided.
2303
+ * @param databaseName - Optional database name (defaults to 'admin')
2304
+ * @returns Database instance
2305
+ * @throws Error if client is not initialized
2306
+ * @internal
2307
+ */
2308
+ private getDatabase(databaseName?: string): Db {
2309
+ if (!this.client) {
2310
+ throw new Error('MongoDB client not initialized. Call initialize() first.');
2311
+ }
2312
+ return this.client.db(databaseName || 'admin');
2313
+ }
2314
+
2315
+ /**
2316
+ * Gets a database instance by name. Defaults to 'admin' if no name provided.
2317
+ * Public method for use by ProgressAPIImpl.
2318
+ * @param databaseName - Optional database name (defaults to 'admin')
2319
+ * @param ref - Optional ref for database resolution
2320
+ * @param type - Optional type for database resolution
2321
+ * @returns Database instance
2322
+ * @throws Error if client is not initialized
2323
+ */
2324
+ public getDatabaseByName(databaseName?: string, ref?: string, type?: string): Db {
2325
+ return this.getDatabase(this.resolveDatabase({ database: databaseName, ref, type }));
2133
2326
  }
2134
2327
  }
2135
2328