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
package/src/simpleMongoHelper.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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: {
|
|
@@ -612,9 +648,12 @@ export class SimpleMongoHelper {
|
|
|
612
648
|
private retryOptions: RetryOptions;
|
|
613
649
|
private config: HelperConfig | null = null;
|
|
614
650
|
public readonly progress: ProgressAPI;
|
|
651
|
+
private cleanupRegistered: boolean = false;
|
|
652
|
+
private isDisconnecting: boolean = false;
|
|
615
653
|
|
|
616
654
|
constructor(connectionString: string, retryOptions?: RetryOptions, config?: HelperConfig) {
|
|
617
|
-
|
|
655
|
+
// Strip database name from connection string if present
|
|
656
|
+
this.connectionString = this.stripDatabaseFromConnectionString(connectionString);
|
|
618
657
|
this.retryOptions = {
|
|
619
658
|
maxRetries: retryOptions?.maxRetries ?? 3,
|
|
620
659
|
retryDelay: retryOptions?.retryDelay ?? 1000,
|
|
@@ -622,6 +661,9 @@ export class SimpleMongoHelper {
|
|
|
622
661
|
};
|
|
623
662
|
this.config = config || null;
|
|
624
663
|
this.progress = new ProgressAPIImpl(this, config?.progress);
|
|
664
|
+
|
|
665
|
+
// Register automatic cleanup on process exit
|
|
666
|
+
this.registerCleanup();
|
|
625
667
|
}
|
|
626
668
|
|
|
627
669
|
/**
|
|
@@ -728,9 +770,8 @@ export class SimpleMongoHelper {
|
|
|
728
770
|
|
|
729
771
|
await testClient.connect();
|
|
730
772
|
|
|
731
|
-
// Try to ping the server
|
|
732
|
-
const
|
|
733
|
-
const testDb = testClient.db(dbName);
|
|
773
|
+
// Try to ping the server using 'admin' database (default)
|
|
774
|
+
const testDb = testClient.db('admin');
|
|
734
775
|
await testDb.admin().ping();
|
|
735
776
|
|
|
736
777
|
// Test basic operation (list collections)
|
|
@@ -859,9 +900,8 @@ export class SimpleMongoHelper {
|
|
|
859
900
|
try {
|
|
860
901
|
this.client = new MongoClient(this.connectionString);
|
|
861
902
|
await this.client.connect();
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
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');
|
|
865
905
|
this.isInitialized = true;
|
|
866
906
|
return;
|
|
867
907
|
} catch (error) {
|
|
@@ -892,12 +932,16 @@ export class SimpleMongoHelper {
|
|
|
892
932
|
async loadCollection<T extends Document = Document>(
|
|
893
933
|
collectionName: string,
|
|
894
934
|
query?: Filter<T>,
|
|
895
|
-
options?: PaginationOptions
|
|
935
|
+
options?: PaginationOptions,
|
|
936
|
+
database?: string,
|
|
937
|
+
ref?: string,
|
|
938
|
+
type?: string
|
|
896
939
|
): Promise<WithId<T>[] | PaginatedResult<T>> {
|
|
897
940
|
this.ensureInitialized();
|
|
898
941
|
|
|
899
942
|
try {
|
|
900
|
-
const
|
|
943
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
944
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
901
945
|
const filter = query || {};
|
|
902
946
|
let cursor = collection.find(filter);
|
|
903
947
|
|
|
@@ -949,12 +993,16 @@ export class SimpleMongoHelper {
|
|
|
949
993
|
async findOne<T extends Document = Document>(
|
|
950
994
|
collectionName: string,
|
|
951
995
|
query: Filter<T>,
|
|
952
|
-
options?: { sort?: Sort; projection?: Document }
|
|
996
|
+
options?: { sort?: Sort; projection?: Document },
|
|
997
|
+
database?: string,
|
|
998
|
+
ref?: string,
|
|
999
|
+
type?: string
|
|
953
1000
|
): Promise<WithId<T> | null> {
|
|
954
1001
|
this.ensureInitialized();
|
|
955
1002
|
|
|
956
1003
|
try {
|
|
957
|
-
const
|
|
1004
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1005
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
958
1006
|
let findOptions: any = {};
|
|
959
1007
|
|
|
960
1008
|
if (options?.sort) {
|
|
@@ -982,12 +1030,16 @@ export class SimpleMongoHelper {
|
|
|
982
1030
|
async insert<T extends Document = Document>(
|
|
983
1031
|
collectionName: string,
|
|
984
1032
|
data: OptionalUnlessRequiredId<T> | OptionalUnlessRequiredId<T>[],
|
|
985
|
-
options?: { session?: ClientSession }
|
|
1033
|
+
options?: { session?: ClientSession },
|
|
1034
|
+
database?: string,
|
|
1035
|
+
ref?: string,
|
|
1036
|
+
type?: string
|
|
986
1037
|
): Promise<any> {
|
|
987
1038
|
this.ensureInitialized();
|
|
988
1039
|
|
|
989
1040
|
try {
|
|
990
|
-
const
|
|
1041
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1042
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
991
1043
|
const insertOptions = options?.session ? { session: options.session } : {};
|
|
992
1044
|
|
|
993
1045
|
if (Array.isArray(data)) {
|
|
@@ -1015,12 +1067,16 @@ export class SimpleMongoHelper {
|
|
|
1015
1067
|
collectionName: string,
|
|
1016
1068
|
filter: Filter<T>,
|
|
1017
1069
|
updateData: UpdateFilter<T>,
|
|
1018
|
-
options?: { upsert?: boolean; multi?: boolean; session?: ClientSession }
|
|
1070
|
+
options?: { upsert?: boolean; multi?: boolean; session?: ClientSession },
|
|
1071
|
+
database?: string,
|
|
1072
|
+
ref?: string,
|
|
1073
|
+
type?: string
|
|
1019
1074
|
): Promise<any> {
|
|
1020
1075
|
this.ensureInitialized();
|
|
1021
1076
|
|
|
1022
1077
|
try {
|
|
1023
|
-
const
|
|
1078
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1079
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1024
1080
|
const updateOptions: any = {};
|
|
1025
1081
|
if (options?.upsert !== undefined) updateOptions.upsert = options.upsert;
|
|
1026
1082
|
if (options?.session) updateOptions.session = options.session;
|
|
@@ -1048,12 +1104,16 @@ export class SimpleMongoHelper {
|
|
|
1048
1104
|
async delete<T extends Document = Document>(
|
|
1049
1105
|
collectionName: string,
|
|
1050
1106
|
filter: Filter<T>,
|
|
1051
|
-
options?: { multi?: boolean }
|
|
1107
|
+
options?: { multi?: boolean },
|
|
1108
|
+
database?: string,
|
|
1109
|
+
ref?: string,
|
|
1110
|
+
type?: string
|
|
1052
1111
|
): Promise<any> {
|
|
1053
1112
|
this.ensureInitialized();
|
|
1054
1113
|
|
|
1055
1114
|
try {
|
|
1056
|
-
const
|
|
1115
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1116
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1057
1117
|
|
|
1058
1118
|
if (options?.multi) {
|
|
1059
1119
|
const result = await collection.deleteMany(filter);
|
|
@@ -1076,12 +1136,16 @@ export class SimpleMongoHelper {
|
|
|
1076
1136
|
*/
|
|
1077
1137
|
async countDocuments<T extends Document = Document>(
|
|
1078
1138
|
collectionName: string,
|
|
1079
|
-
query?: Filter<T
|
|
1139
|
+
query?: Filter<T>,
|
|
1140
|
+
database?: string,
|
|
1141
|
+
ref?: string,
|
|
1142
|
+
type?: string
|
|
1080
1143
|
): Promise<number> {
|
|
1081
1144
|
this.ensureInitialized();
|
|
1082
1145
|
|
|
1083
1146
|
try {
|
|
1084
|
-
const
|
|
1147
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1148
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1085
1149
|
const count = await collection.countDocuments(query || {});
|
|
1086
1150
|
return count;
|
|
1087
1151
|
} catch (error) {
|
|
@@ -1095,11 +1159,12 @@ export class SimpleMongoHelper {
|
|
|
1095
1159
|
* @returns Estimated number of documents
|
|
1096
1160
|
* @throws Error if not initialized or if count fails
|
|
1097
1161
|
*/
|
|
1098
|
-
async estimatedDocumentCount(collectionName: string): Promise<number> {
|
|
1162
|
+
async estimatedDocumentCount(collectionName: string, database?: string, ref?: string, type?: string): Promise<number> {
|
|
1099
1163
|
this.ensureInitialized();
|
|
1100
1164
|
|
|
1101
1165
|
try {
|
|
1102
|
-
const
|
|
1166
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1167
|
+
const collection = db.collection(collectionName);
|
|
1103
1168
|
const count = await collection.estimatedDocumentCount();
|
|
1104
1169
|
return count;
|
|
1105
1170
|
} catch (error) {
|
|
@@ -1116,12 +1181,16 @@ export class SimpleMongoHelper {
|
|
|
1116
1181
|
*/
|
|
1117
1182
|
async aggregate<T extends Document = Document>(
|
|
1118
1183
|
collectionName: string,
|
|
1119
|
-
pipeline: Document[]
|
|
1184
|
+
pipeline: Document[],
|
|
1185
|
+
database?: string,
|
|
1186
|
+
ref?: string,
|
|
1187
|
+
type?: string
|
|
1120
1188
|
): Promise<T[]> {
|
|
1121
1189
|
this.ensureInitialized();
|
|
1122
1190
|
|
|
1123
1191
|
try {
|
|
1124
|
-
const
|
|
1192
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1193
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1125
1194
|
const results = await collection.aggregate<T>(pipeline).toArray();
|
|
1126
1195
|
return results;
|
|
1127
1196
|
} catch (error) {
|
|
@@ -1140,12 +1209,16 @@ export class SimpleMongoHelper {
|
|
|
1140
1209
|
async createIndex(
|
|
1141
1210
|
collectionName: string,
|
|
1142
1211
|
indexSpec: IndexSpecification,
|
|
1143
|
-
options?: CreateIndexesOptions
|
|
1212
|
+
options?: CreateIndexesOptions,
|
|
1213
|
+
database?: string,
|
|
1214
|
+
ref?: string,
|
|
1215
|
+
type?: string
|
|
1144
1216
|
): Promise<string> {
|
|
1145
1217
|
this.ensureInitialized();
|
|
1146
1218
|
|
|
1147
1219
|
try {
|
|
1148
|
-
const
|
|
1220
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1221
|
+
const collection = db.collection(collectionName);
|
|
1149
1222
|
const indexName = await collection.createIndex(indexSpec, options);
|
|
1150
1223
|
return indexName;
|
|
1151
1224
|
} catch (error) {
|
|
@@ -1160,11 +1233,12 @@ export class SimpleMongoHelper {
|
|
|
1160
1233
|
* @returns Result object
|
|
1161
1234
|
* @throws Error if not initialized or if index drop fails
|
|
1162
1235
|
*/
|
|
1163
|
-
async dropIndex(collectionName: string, indexName: string): Promise<any> {
|
|
1236
|
+
async dropIndex(collectionName: string, indexName: string, database?: string, ref?: string, type?: string): Promise<any> {
|
|
1164
1237
|
this.ensureInitialized();
|
|
1165
1238
|
|
|
1166
1239
|
try {
|
|
1167
|
-
const
|
|
1240
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1241
|
+
const collection = db.collection(collectionName);
|
|
1168
1242
|
const result = await collection.dropIndex(indexName);
|
|
1169
1243
|
return result;
|
|
1170
1244
|
} catch (error) {
|
|
@@ -1178,11 +1252,12 @@ export class SimpleMongoHelper {
|
|
|
1178
1252
|
* @returns Array of index information
|
|
1179
1253
|
* @throws Error if not initialized or if listing fails
|
|
1180
1254
|
*/
|
|
1181
|
-
async listIndexes(collectionName: string): Promise<Document[]> {
|
|
1255
|
+
async listIndexes(collectionName: string, database?: string, ref?: string, type?: string): Promise<Document[]> {
|
|
1182
1256
|
this.ensureInitialized();
|
|
1183
1257
|
|
|
1184
1258
|
try {
|
|
1185
|
-
const
|
|
1259
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1260
|
+
const collection = db.collection(collectionName);
|
|
1186
1261
|
const indexes = await collection.indexes();
|
|
1187
1262
|
return indexes;
|
|
1188
1263
|
} catch (error) {
|
|
@@ -1244,7 +1319,7 @@ export class SimpleMongoHelper {
|
|
|
1244
1319
|
*/
|
|
1245
1320
|
async loadByRef<T extends Document = Document>(
|
|
1246
1321
|
ref: string,
|
|
1247
|
-
options?: PaginationOptions & { session?: ClientSession }
|
|
1322
|
+
options?: PaginationOptions & { session?: ClientSession; database?: string; ref?: string; type?: string }
|
|
1248
1323
|
): Promise<WithId<T>[] | PaginatedResult<T>> {
|
|
1249
1324
|
this.ensureInitialized();
|
|
1250
1325
|
|
|
@@ -1257,9 +1332,13 @@ export class SimpleMongoHelper {
|
|
|
1257
1332
|
throw new Error(`Ref '${ref}' not found in configuration inputs.`);
|
|
1258
1333
|
}
|
|
1259
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 }));
|
|
1260
1339
|
// Note: loadCollection doesn't support session yet, but we'll pass it through options
|
|
1261
1340
|
// For now, we'll use the collection directly with session support
|
|
1262
|
-
const collection: Collection<T> =
|
|
1341
|
+
const collection: Collection<T> = db.collection<T>(inputConfig.collection);
|
|
1263
1342
|
const filter = inputConfig.query || {};
|
|
1264
1343
|
const session = options?.session;
|
|
1265
1344
|
|
|
@@ -1323,15 +1402,22 @@ export class SimpleMongoHelper {
|
|
|
1323
1402
|
options?: {
|
|
1324
1403
|
fieldName?: string;
|
|
1325
1404
|
unique?: boolean;
|
|
1405
|
+
database?: string;
|
|
1406
|
+
ref?: string;
|
|
1407
|
+
type?: string;
|
|
1326
1408
|
}
|
|
1327
1409
|
): Promise<EnsureSignatureIndexResult> {
|
|
1328
1410
|
this.ensureInitialized();
|
|
1329
1411
|
|
|
1330
1412
|
const fieldName = options?.fieldName || '_sig';
|
|
1331
1413
|
const unique = options?.unique !== false; // Default to true
|
|
1414
|
+
const database = options?.database;
|
|
1415
|
+
const ref = options?.ref;
|
|
1416
|
+
const type = options?.type;
|
|
1332
1417
|
|
|
1333
1418
|
try {
|
|
1334
|
-
const
|
|
1419
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1420
|
+
const collection = db.collection(collectionName);
|
|
1335
1421
|
const indexes = await collection.indexes();
|
|
1336
1422
|
|
|
1337
1423
|
// Find existing index on the signature field
|
|
@@ -1397,6 +1483,9 @@ export class SimpleMongoHelper {
|
|
|
1397
1483
|
options?: {
|
|
1398
1484
|
session?: ClientSession;
|
|
1399
1485
|
ensureIndex?: boolean;
|
|
1486
|
+
database?: string;
|
|
1487
|
+
ref?: string;
|
|
1488
|
+
type?: string;
|
|
1400
1489
|
}
|
|
1401
1490
|
): Promise<WriteByRefResult> {
|
|
1402
1491
|
this.ensureInitialized();
|
|
@@ -1415,6 +1504,9 @@ export class SimpleMongoHelper {
|
|
|
1415
1504
|
const mode = outputConfig.mode || this.config.output?.mode || 'append';
|
|
1416
1505
|
const ensureIndex = options?.ensureIndex !== false; // Default to true
|
|
1417
1506
|
const session = options?.session;
|
|
1507
|
+
const database = options?.database;
|
|
1508
|
+
const dbRef = options?.ref;
|
|
1509
|
+
const dbType = options?.type;
|
|
1418
1510
|
|
|
1419
1511
|
const result: WriteByRefResult = {
|
|
1420
1512
|
inserted: 0,
|
|
@@ -1424,7 +1516,8 @@ export class SimpleMongoHelper {
|
|
|
1424
1516
|
};
|
|
1425
1517
|
|
|
1426
1518
|
try {
|
|
1427
|
-
const
|
|
1519
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
|
|
1520
|
+
const collection = db.collection(collectionName);
|
|
1428
1521
|
|
|
1429
1522
|
// Handle replace mode: clear collection first
|
|
1430
1523
|
if (mode === 'replace') {
|
|
@@ -1590,6 +1683,9 @@ export class SimpleMongoHelper {
|
|
|
1590
1683
|
const writeResult = await this.writeByRef(ref, documents, {
|
|
1591
1684
|
session: options?.session,
|
|
1592
1685
|
ensureIndex: options?.ensureIndex,
|
|
1686
|
+
database: options?.database,
|
|
1687
|
+
ref: options?.ref,
|
|
1688
|
+
type: options?.type,
|
|
1593
1689
|
});
|
|
1594
1690
|
|
|
1595
1691
|
const result: WriteStageResult = {
|
|
@@ -1608,7 +1704,7 @@ export class SimpleMongoHelper {
|
|
|
1608
1704
|
provider: options.complete.provider,
|
|
1609
1705
|
metadata: options.complete.metadata,
|
|
1610
1706
|
},
|
|
1611
|
-
{ session: options.session }
|
|
1707
|
+
{ session: options.session, database: options?.database, ref: options?.ref, type: options?.type }
|
|
1612
1708
|
);
|
|
1613
1709
|
result.completed = true;
|
|
1614
1710
|
} catch (error) {
|
|
@@ -1646,6 +1742,9 @@ export class SimpleMongoHelper {
|
|
|
1646
1742
|
onUnmatched1 = 'include',
|
|
1647
1743
|
onUnmatched2 = 'include',
|
|
1648
1744
|
session,
|
|
1745
|
+
database,
|
|
1746
|
+
ref,
|
|
1747
|
+
type,
|
|
1649
1748
|
} = options;
|
|
1650
1749
|
|
|
1651
1750
|
// Determine join behavior from joinType or fallback to onUnmatched flags
|
|
@@ -1689,9 +1788,10 @@ export class SimpleMongoHelper {
|
|
|
1689
1788
|
};
|
|
1690
1789
|
|
|
1691
1790
|
try {
|
|
1692
|
-
const
|
|
1693
|
-
const
|
|
1694
|
-
const
|
|
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);
|
|
1695
1795
|
|
|
1696
1796
|
const findOptions: any = {};
|
|
1697
1797
|
if (session) {
|
|
@@ -1982,22 +2082,109 @@ export class SimpleMongoHelper {
|
|
|
1982
2082
|
}
|
|
1983
2083
|
}
|
|
1984
2084
|
|
|
2085
|
+
/**
|
|
2086
|
+
* Registers automatic cleanup handlers for process exit events.
|
|
2087
|
+
* This ensures connections are closed gracefully when the application terminates.
|
|
2088
|
+
* Uses a global registry to handle multiple instances without conflicts.
|
|
2089
|
+
* @internal
|
|
2090
|
+
*/
|
|
2091
|
+
private registerCleanup(): void {
|
|
2092
|
+
if (this.cleanupRegistered) {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
this.cleanupRegistered = true;
|
|
2096
|
+
|
|
2097
|
+
// Global registry for all SimpleMongoHelper instances
|
|
2098
|
+
const globalRegistry = (global as any).__nxMongoHelperInstances || new Set<SimpleMongoHelper>();
|
|
2099
|
+
(global as any).__nxMongoHelperInstances = globalRegistry;
|
|
2100
|
+
globalRegistry.add(this);
|
|
2101
|
+
|
|
2102
|
+
// Register global cleanup handlers only once
|
|
2103
|
+
if (!(global as any).__nxMongoCleanupRegistered) {
|
|
2104
|
+
(global as any).__nxMongoCleanupRegistered = true;
|
|
2105
|
+
|
|
2106
|
+
if (typeof process !== 'undefined') {
|
|
2107
|
+
const cleanupAll = async (signal?: string) => {
|
|
2108
|
+
const instances = (global as any).__nxMongoHelperInstances as Set<SimpleMongoHelper>;
|
|
2109
|
+
if (!instances || instances.size === 0) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// Cleanup all instances in parallel with timeout
|
|
2114
|
+
const cleanupPromises = Array.from(instances).map(async (instance: SimpleMongoHelper) => {
|
|
2115
|
+
try {
|
|
2116
|
+
// Use the public disconnect method, but with timeout protection
|
|
2117
|
+
const timeout = new Promise((_, reject) =>
|
|
2118
|
+
setTimeout(() => reject(new Error('Disconnect timeout')), 5000)
|
|
2119
|
+
);
|
|
2120
|
+
|
|
2121
|
+
await Promise.race([
|
|
2122
|
+
instance.disconnect(),
|
|
2123
|
+
timeout
|
|
2124
|
+
]).catch(() => {
|
|
2125
|
+
// Ignore timeout errors during automatic cleanup
|
|
2126
|
+
});
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
// Silently ignore errors during automatic cleanup
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
|
|
2132
|
+
await Promise.all(cleanupPromises).catch(() => {
|
|
2133
|
+
// Ignore errors during cleanup
|
|
2134
|
+
});
|
|
2135
|
+
};
|
|
2136
|
+
|
|
2137
|
+
// Graceful shutdown signals
|
|
2138
|
+
process.once('SIGINT', () => {
|
|
2139
|
+
cleanupAll('SIGINT').finally(() => {
|
|
2140
|
+
process.exit(0);
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
process.once('SIGTERM', () => {
|
|
2145
|
+
cleanupAll('SIGTERM').finally(() => {
|
|
2146
|
+
process.exit(0);
|
|
2147
|
+
});
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
// Before exit (allows async cleanup)
|
|
2151
|
+
process.once('beforeExit', () => {
|
|
2152
|
+
cleanupAll('beforeExit');
|
|
2153
|
+
});
|
|
2154
|
+
|
|
2155
|
+
// Note: We don't handle uncaughtException/unhandledRejection here
|
|
2156
|
+
// as those should be handled by the application, not the library
|
|
2157
|
+
// The beforeExit handler will still clean up connections
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
1985
2162
|
/**
|
|
1986
2163
|
* Closes the MongoDB connection and cleans up resources.
|
|
1987
|
-
*
|
|
2164
|
+
* This method is called automatically on process exit, but can also be called manually.
|
|
2165
|
+
* @throws Error if disconnect fails (only when called manually, not during automatic cleanup)
|
|
1988
2166
|
*/
|
|
1989
2167
|
async disconnect(): Promise<void> {
|
|
2168
|
+
if (this.isDisconnecting) {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
1990
2172
|
if (!this.isInitialized || !this.client) {
|
|
1991
2173
|
return;
|
|
1992
2174
|
}
|
|
1993
2175
|
|
|
2176
|
+
this.isDisconnecting = true;
|
|
2177
|
+
|
|
1994
2178
|
try {
|
|
1995
2179
|
await this.client.close();
|
|
1996
2180
|
this.client = null;
|
|
1997
2181
|
this.db = null;
|
|
1998
2182
|
this.isInitialized = false;
|
|
1999
2183
|
} catch (error) {
|
|
2184
|
+
this.isDisconnecting = false;
|
|
2000
2185
|
throw new Error(`Failed to disconnect: ${error instanceof Error ? error.message : String(error)}`);
|
|
2186
|
+
} finally {
|
|
2187
|
+
this.isDisconnecting = false;
|
|
2001
2188
|
}
|
|
2002
2189
|
}
|
|
2003
2190
|
|
|
@@ -2024,20 +2211,118 @@ export class SimpleMongoHelper {
|
|
|
2024
2211
|
}
|
|
2025
2212
|
|
|
2026
2213
|
/**
|
|
2027
|
-
*
|
|
2028
|
-
* @param connectionString - MongoDB connection string
|
|
2029
|
-
* @returns
|
|
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/'
|
|
2030
2220
|
*/
|
|
2031
|
-
private
|
|
2221
|
+
private stripDatabaseFromConnectionString(connectionString: string): string {
|
|
2032
2222
|
try {
|
|
2033
2223
|
const url = new URL(connectionString);
|
|
2034
|
-
|
|
2035
|
-
|
|
2224
|
+
// Remove pathname (database name) but keep trailing slash
|
|
2225
|
+
url.pathname = '/';
|
|
2226
|
+
return url.toString();
|
|
2036
2227
|
} catch {
|
|
2037
|
-
// If URL parsing fails, try
|
|
2038
|
-
|
|
2039
|
-
|
|
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;
|
|
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;
|
|
2040
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 }));
|
|
2041
2326
|
}
|
|
2042
2327
|
}
|
|
2043
2328
|
|