nx-mongo 3.6.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -22
- package/dist/simpleMongoHelper.d.ts +68 -15
- package/dist/simpleMongoHelper.d.ts.map +1 -1
- package/dist/simpleMongoHelper.js +263 -83
- package/dist/simpleMongoHelper.js.map +1 -1
- package/package.json +1 -1
- package/src/simpleMongoHelper.ts +319 -84
- package/test-connection.ts +47 -0
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: {
|
|
@@ -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
|
-
|
|
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
|
|
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)
|
|
@@ -769,81 +805,123 @@ export class SimpleMongoHelper {
|
|
|
769
805
|
}
|
|
770
806
|
}
|
|
771
807
|
|
|
772
|
-
|
|
773
|
-
|
|
808
|
+
// Extract error information more comprehensively
|
|
809
|
+
let errorMessage = 'Unknown error';
|
|
810
|
+
let errorName = 'Unknown';
|
|
811
|
+
let errorCode: string | number | undefined;
|
|
812
|
+
|
|
813
|
+
if (error instanceof Error) {
|
|
814
|
+
errorMessage = error.message || 'Unknown error';
|
|
815
|
+
errorName = error.name || 'Error';
|
|
816
|
+
// Check for MongoDB-specific error properties
|
|
817
|
+
const mongoError = error as any;
|
|
818
|
+
if (mongoError.code) {
|
|
819
|
+
errorCode = mongoError.code;
|
|
820
|
+
}
|
|
821
|
+
if (mongoError.codeName) {
|
|
822
|
+
errorName = mongoError.codeName;
|
|
823
|
+
}
|
|
824
|
+
if (mongoError.errmsg) {
|
|
825
|
+
errorMessage = mongoError.errmsg;
|
|
826
|
+
}
|
|
827
|
+
} else if (error && typeof error === 'object') {
|
|
828
|
+
// Try to extract meaningful information from error object
|
|
829
|
+
const errObj = error as any;
|
|
830
|
+
errorMessage = errObj.message || errObj.errmsg || errObj.error || JSON.stringify(error);
|
|
831
|
+
errorName = errObj.name || errObj.codeName || 'Error';
|
|
832
|
+
errorCode = errObj.code;
|
|
833
|
+
} else {
|
|
834
|
+
errorMessage = String(error);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const errorMessageLower = errorMessage.toLowerCase();
|
|
838
|
+
|
|
839
|
+
// Check for Windows-specific localhost resolution issues
|
|
840
|
+
const isLocalhost = this.connectionString.includes('localhost');
|
|
841
|
+
const windowsHint = isLocalhost && process.platform === 'win32'
|
|
842
|
+
? ' On Windows, try using 127.0.0.1 instead of localhost to avoid IPv6 resolution issues.'
|
|
843
|
+
: '';
|
|
774
844
|
|
|
775
845
|
// Analyze error type
|
|
776
|
-
if (
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
846
|
+
if (errorMessageLower.includes('authentication failed') ||
|
|
847
|
+
errorMessageLower.includes('auth failed') ||
|
|
848
|
+
errorMessageLower.includes('invalid credentials') ||
|
|
849
|
+
errorMessageLower.includes('authentication failed') ||
|
|
850
|
+
errorCode === 18 || // MongoDB error code for authentication failed
|
|
851
|
+
errorName === 'MongoAuthenticationError') {
|
|
780
852
|
return {
|
|
781
853
|
success: false,
|
|
782
854
|
error: {
|
|
783
855
|
type: 'authentication_failed',
|
|
784
856
|
message: 'Authentication failed',
|
|
785
|
-
details: `Invalid username or password. Error: ${errorMessage}. Verify credentials in connection string.`
|
|
857
|
+
details: `Invalid username or password. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}. Verify credentials in connection string.`
|
|
786
858
|
}
|
|
787
859
|
};
|
|
788
860
|
}
|
|
789
861
|
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
|
|
862
|
+
if (errorMessageLower.includes('timeout') ||
|
|
863
|
+
errorMessageLower.includes('etimedout') ||
|
|
864
|
+
errorCode === 'ETIMEDOUT' ||
|
|
865
|
+
errorName === 'MongoServerSelectionError') {
|
|
793
866
|
return {
|
|
794
867
|
success: false,
|
|
795
868
|
error: {
|
|
796
869
|
type: 'connection_failed',
|
|
797
870
|
message: 'Connection timeout',
|
|
798
|
-
details: `Cannot reach MongoDB server. Error: ${errorMessage}. Check if server is running, host/port is correct, and firewall allows connections
|
|
871
|
+
details: `Cannot reach MongoDB server. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}. Check if server is running, host/port is correct, and firewall allows connections.${windowsHint}`
|
|
799
872
|
}
|
|
800
873
|
};
|
|
801
874
|
}
|
|
802
875
|
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
|
|
876
|
+
if (errorMessageLower.includes('dns') ||
|
|
877
|
+
errorMessageLower.includes('enotfound') ||
|
|
878
|
+
errorMessageLower.includes('getaddrinfo') ||
|
|
879
|
+
errorCode === 'ENOTFOUND') {
|
|
806
880
|
return {
|
|
807
881
|
success: false,
|
|
808
882
|
error: {
|
|
809
883
|
type: 'connection_failed',
|
|
810
884
|
message: 'DNS resolution failed',
|
|
811
|
-
details: `Cannot resolve hostname. Error: ${errorMessage}. Check if hostname is correct and DNS is configured properly
|
|
885
|
+
details: `Cannot resolve hostname. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}. Check if hostname is correct and DNS is configured properly.${windowsHint}`
|
|
812
886
|
}
|
|
813
887
|
};
|
|
814
888
|
}
|
|
815
889
|
|
|
816
|
-
if (
|
|
817
|
-
|
|
890
|
+
if (errorMessageLower.includes('refused') ||
|
|
891
|
+
errorMessageLower.includes('econnrefused') ||
|
|
892
|
+
errorCode === 'ECONNREFUSED') {
|
|
818
893
|
return {
|
|
819
894
|
success: false,
|
|
820
895
|
error: {
|
|
821
896
|
type: 'connection_failed',
|
|
822
897
|
message: 'Connection refused',
|
|
823
|
-
details: `MongoDB server refused connection. Error: ${errorMessage}. Check if MongoDB is running on the specified host and port
|
|
898
|
+
details: `MongoDB server refused connection. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}. Check if MongoDB is running on the specified host and port.${windowsHint}`
|
|
824
899
|
}
|
|
825
900
|
};
|
|
826
901
|
}
|
|
827
902
|
|
|
828
|
-
if (
|
|
829
|
-
|
|
903
|
+
if (errorMessageLower.includes('network') ||
|
|
904
|
+
errorMessageLower.includes('network') ||
|
|
905
|
+
errorName === 'MongoNetworkError') {
|
|
830
906
|
return {
|
|
831
907
|
success: false,
|
|
832
908
|
error: {
|
|
833
909
|
type: 'connection_failed',
|
|
834
910
|
message: 'Network error',
|
|
835
|
-
details: `Network connectivity issue. Error: ${errorMessage}. Check network connection and firewall settings
|
|
911
|
+
details: `Network connectivity issue. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}. Check network connection and firewall settings.${windowsHint}`
|
|
836
912
|
}
|
|
837
913
|
};
|
|
838
914
|
}
|
|
839
915
|
|
|
840
|
-
// Generic connection error
|
|
916
|
+
// Generic connection error with full details
|
|
917
|
+
const details = `Failed to connect to MongoDB. Error: ${errorMessage}${errorCode ? ` (Code: ${errorCode})` : ''}${errorName !== 'Error' ? ` (Type: ${errorName})` : ''}. Verify connection string, server status, and network connectivity.${windowsHint}`;
|
|
918
|
+
|
|
841
919
|
return {
|
|
842
920
|
success: false,
|
|
843
921
|
error: {
|
|
844
922
|
type: 'connection_failed',
|
|
845
923
|
message: 'Connection failed',
|
|
846
|
-
details
|
|
924
|
+
details
|
|
847
925
|
}
|
|
848
926
|
};
|
|
849
927
|
}
|
|
@@ -864,9 +942,8 @@ export class SimpleMongoHelper {
|
|
|
864
942
|
try {
|
|
865
943
|
this.client = new MongoClient(this.connectionString);
|
|
866
944
|
await this.client.connect();
|
|
867
|
-
//
|
|
868
|
-
|
|
869
|
-
this.db = this.client.db(dbName);
|
|
945
|
+
// Default to 'admin' database for initial connection (not used for operations)
|
|
946
|
+
this.db = this.client.db('admin');
|
|
870
947
|
this.isInitialized = true;
|
|
871
948
|
return;
|
|
872
949
|
} catch (error) {
|
|
@@ -897,12 +974,16 @@ export class SimpleMongoHelper {
|
|
|
897
974
|
async loadCollection<T extends Document = Document>(
|
|
898
975
|
collectionName: string,
|
|
899
976
|
query?: Filter<T>,
|
|
900
|
-
options?: PaginationOptions
|
|
977
|
+
options?: PaginationOptions,
|
|
978
|
+
database?: string,
|
|
979
|
+
ref?: string,
|
|
980
|
+
type?: string
|
|
901
981
|
): Promise<WithId<T>[] | PaginatedResult<T>> {
|
|
902
982
|
this.ensureInitialized();
|
|
903
983
|
|
|
904
984
|
try {
|
|
905
|
-
const
|
|
985
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
986
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
906
987
|
const filter = query || {};
|
|
907
988
|
let cursor = collection.find(filter);
|
|
908
989
|
|
|
@@ -954,12 +1035,16 @@ export class SimpleMongoHelper {
|
|
|
954
1035
|
async findOne<T extends Document = Document>(
|
|
955
1036
|
collectionName: string,
|
|
956
1037
|
query: Filter<T>,
|
|
957
|
-
options?: { sort?: Sort; projection?: Document }
|
|
1038
|
+
options?: { sort?: Sort; projection?: Document },
|
|
1039
|
+
database?: string,
|
|
1040
|
+
ref?: string,
|
|
1041
|
+
type?: string
|
|
958
1042
|
): Promise<WithId<T> | null> {
|
|
959
1043
|
this.ensureInitialized();
|
|
960
1044
|
|
|
961
1045
|
try {
|
|
962
|
-
const
|
|
1046
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1047
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
963
1048
|
let findOptions: any = {};
|
|
964
1049
|
|
|
965
1050
|
if (options?.sort) {
|
|
@@ -987,12 +1072,16 @@ export class SimpleMongoHelper {
|
|
|
987
1072
|
async insert<T extends Document = Document>(
|
|
988
1073
|
collectionName: string,
|
|
989
1074
|
data: OptionalUnlessRequiredId<T> | OptionalUnlessRequiredId<T>[],
|
|
990
|
-
options?: { session?: ClientSession }
|
|
1075
|
+
options?: { session?: ClientSession },
|
|
1076
|
+
database?: string,
|
|
1077
|
+
ref?: string,
|
|
1078
|
+
type?: string
|
|
991
1079
|
): Promise<any> {
|
|
992
1080
|
this.ensureInitialized();
|
|
993
1081
|
|
|
994
1082
|
try {
|
|
995
|
-
const
|
|
1083
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1084
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
996
1085
|
const insertOptions = options?.session ? { session: options.session } : {};
|
|
997
1086
|
|
|
998
1087
|
if (Array.isArray(data)) {
|
|
@@ -1020,12 +1109,16 @@ export class SimpleMongoHelper {
|
|
|
1020
1109
|
collectionName: string,
|
|
1021
1110
|
filter: Filter<T>,
|
|
1022
1111
|
updateData: UpdateFilter<T>,
|
|
1023
|
-
options?: { upsert?: boolean; multi?: boolean; session?: ClientSession }
|
|
1112
|
+
options?: { upsert?: boolean; multi?: boolean; session?: ClientSession },
|
|
1113
|
+
database?: string,
|
|
1114
|
+
ref?: string,
|
|
1115
|
+
type?: string
|
|
1024
1116
|
): Promise<any> {
|
|
1025
1117
|
this.ensureInitialized();
|
|
1026
1118
|
|
|
1027
1119
|
try {
|
|
1028
|
-
const
|
|
1120
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1121
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1029
1122
|
const updateOptions: any = {};
|
|
1030
1123
|
if (options?.upsert !== undefined) updateOptions.upsert = options.upsert;
|
|
1031
1124
|
if (options?.session) updateOptions.session = options.session;
|
|
@@ -1053,12 +1146,16 @@ export class SimpleMongoHelper {
|
|
|
1053
1146
|
async delete<T extends Document = Document>(
|
|
1054
1147
|
collectionName: string,
|
|
1055
1148
|
filter: Filter<T>,
|
|
1056
|
-
options?: { multi?: boolean }
|
|
1149
|
+
options?: { multi?: boolean },
|
|
1150
|
+
database?: string,
|
|
1151
|
+
ref?: string,
|
|
1152
|
+
type?: string
|
|
1057
1153
|
): Promise<any> {
|
|
1058
1154
|
this.ensureInitialized();
|
|
1059
1155
|
|
|
1060
1156
|
try {
|
|
1061
|
-
const
|
|
1157
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1158
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1062
1159
|
|
|
1063
1160
|
if (options?.multi) {
|
|
1064
1161
|
const result = await collection.deleteMany(filter);
|
|
@@ -1081,12 +1178,16 @@ export class SimpleMongoHelper {
|
|
|
1081
1178
|
*/
|
|
1082
1179
|
async countDocuments<T extends Document = Document>(
|
|
1083
1180
|
collectionName: string,
|
|
1084
|
-
query?: Filter<T
|
|
1181
|
+
query?: Filter<T>,
|
|
1182
|
+
database?: string,
|
|
1183
|
+
ref?: string,
|
|
1184
|
+
type?: string
|
|
1085
1185
|
): Promise<number> {
|
|
1086
1186
|
this.ensureInitialized();
|
|
1087
1187
|
|
|
1088
1188
|
try {
|
|
1089
|
-
const
|
|
1189
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1190
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1090
1191
|
const count = await collection.countDocuments(query || {});
|
|
1091
1192
|
return count;
|
|
1092
1193
|
} catch (error) {
|
|
@@ -1100,11 +1201,12 @@ export class SimpleMongoHelper {
|
|
|
1100
1201
|
* @returns Estimated number of documents
|
|
1101
1202
|
* @throws Error if not initialized or if count fails
|
|
1102
1203
|
*/
|
|
1103
|
-
async estimatedDocumentCount(collectionName: string): Promise<number> {
|
|
1204
|
+
async estimatedDocumentCount(collectionName: string, database?: string, ref?: string, type?: string): Promise<number> {
|
|
1104
1205
|
this.ensureInitialized();
|
|
1105
1206
|
|
|
1106
1207
|
try {
|
|
1107
|
-
const
|
|
1208
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1209
|
+
const collection = db.collection(collectionName);
|
|
1108
1210
|
const count = await collection.estimatedDocumentCount();
|
|
1109
1211
|
return count;
|
|
1110
1212
|
} catch (error) {
|
|
@@ -1121,12 +1223,16 @@ export class SimpleMongoHelper {
|
|
|
1121
1223
|
*/
|
|
1122
1224
|
async aggregate<T extends Document = Document>(
|
|
1123
1225
|
collectionName: string,
|
|
1124
|
-
pipeline: Document[]
|
|
1226
|
+
pipeline: Document[],
|
|
1227
|
+
database?: string,
|
|
1228
|
+
ref?: string,
|
|
1229
|
+
type?: string
|
|
1125
1230
|
): Promise<T[]> {
|
|
1126
1231
|
this.ensureInitialized();
|
|
1127
1232
|
|
|
1128
1233
|
try {
|
|
1129
|
-
const
|
|
1234
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1235
|
+
const collection: Collection<T> = db.collection<T>(collectionName);
|
|
1130
1236
|
const results = await collection.aggregate<T>(pipeline).toArray();
|
|
1131
1237
|
return results;
|
|
1132
1238
|
} catch (error) {
|
|
@@ -1145,12 +1251,16 @@ export class SimpleMongoHelper {
|
|
|
1145
1251
|
async createIndex(
|
|
1146
1252
|
collectionName: string,
|
|
1147
1253
|
indexSpec: IndexSpecification,
|
|
1148
|
-
options?: CreateIndexesOptions
|
|
1254
|
+
options?: CreateIndexesOptions,
|
|
1255
|
+
database?: string,
|
|
1256
|
+
ref?: string,
|
|
1257
|
+
type?: string
|
|
1149
1258
|
): Promise<string> {
|
|
1150
1259
|
this.ensureInitialized();
|
|
1151
1260
|
|
|
1152
1261
|
try {
|
|
1153
|
-
const
|
|
1262
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1263
|
+
const collection = db.collection(collectionName);
|
|
1154
1264
|
const indexName = await collection.createIndex(indexSpec, options);
|
|
1155
1265
|
return indexName;
|
|
1156
1266
|
} catch (error) {
|
|
@@ -1165,11 +1275,12 @@ export class SimpleMongoHelper {
|
|
|
1165
1275
|
* @returns Result object
|
|
1166
1276
|
* @throws Error if not initialized or if index drop fails
|
|
1167
1277
|
*/
|
|
1168
|
-
async dropIndex(collectionName: string, indexName: string): Promise<any> {
|
|
1278
|
+
async dropIndex(collectionName: string, indexName: string, database?: string, ref?: string, type?: string): Promise<any> {
|
|
1169
1279
|
this.ensureInitialized();
|
|
1170
1280
|
|
|
1171
1281
|
try {
|
|
1172
|
-
const
|
|
1282
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1283
|
+
const collection = db.collection(collectionName);
|
|
1173
1284
|
const result = await collection.dropIndex(indexName);
|
|
1174
1285
|
return result;
|
|
1175
1286
|
} catch (error) {
|
|
@@ -1183,11 +1294,12 @@ export class SimpleMongoHelper {
|
|
|
1183
1294
|
* @returns Array of index information
|
|
1184
1295
|
* @throws Error if not initialized or if listing fails
|
|
1185
1296
|
*/
|
|
1186
|
-
async listIndexes(collectionName: string): Promise<Document[]> {
|
|
1297
|
+
async listIndexes(collectionName: string, database?: string, ref?: string, type?: string): Promise<Document[]> {
|
|
1187
1298
|
this.ensureInitialized();
|
|
1188
1299
|
|
|
1189
1300
|
try {
|
|
1190
|
-
const
|
|
1301
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1302
|
+
const collection = db.collection(collectionName);
|
|
1191
1303
|
const indexes = await collection.indexes();
|
|
1192
1304
|
return indexes;
|
|
1193
1305
|
} catch (error) {
|
|
@@ -1249,7 +1361,7 @@ export class SimpleMongoHelper {
|
|
|
1249
1361
|
*/
|
|
1250
1362
|
async loadByRef<T extends Document = Document>(
|
|
1251
1363
|
ref: string,
|
|
1252
|
-
options?: PaginationOptions & { session?: ClientSession }
|
|
1364
|
+
options?: PaginationOptions & { session?: ClientSession; database?: string; ref?: string; type?: string }
|
|
1253
1365
|
): Promise<WithId<T>[] | PaginatedResult<T>> {
|
|
1254
1366
|
this.ensureInitialized();
|
|
1255
1367
|
|
|
@@ -1262,9 +1374,13 @@ export class SimpleMongoHelper {
|
|
|
1262
1374
|
throw new Error(`Ref '${ref}' not found in configuration inputs.`);
|
|
1263
1375
|
}
|
|
1264
1376
|
|
|
1377
|
+
const database = options?.database;
|
|
1378
|
+
const dbRef = options?.ref;
|
|
1379
|
+
const dbType = options?.type;
|
|
1380
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
|
|
1265
1381
|
// Note: loadCollection doesn't support session yet, but we'll pass it through options
|
|
1266
1382
|
// For now, we'll use the collection directly with session support
|
|
1267
|
-
const collection: Collection<T> =
|
|
1383
|
+
const collection: Collection<T> = db.collection<T>(inputConfig.collection);
|
|
1268
1384
|
const filter = inputConfig.query || {};
|
|
1269
1385
|
const session = options?.session;
|
|
1270
1386
|
|
|
@@ -1328,15 +1444,22 @@ export class SimpleMongoHelper {
|
|
|
1328
1444
|
options?: {
|
|
1329
1445
|
fieldName?: string;
|
|
1330
1446
|
unique?: boolean;
|
|
1447
|
+
database?: string;
|
|
1448
|
+
ref?: string;
|
|
1449
|
+
type?: string;
|
|
1331
1450
|
}
|
|
1332
1451
|
): Promise<EnsureSignatureIndexResult> {
|
|
1333
1452
|
this.ensureInitialized();
|
|
1334
1453
|
|
|
1335
1454
|
const fieldName = options?.fieldName || '_sig';
|
|
1336
1455
|
const unique = options?.unique !== false; // Default to true
|
|
1456
|
+
const database = options?.database;
|
|
1457
|
+
const ref = options?.ref;
|
|
1458
|
+
const type = options?.type;
|
|
1337
1459
|
|
|
1338
1460
|
try {
|
|
1339
|
-
const
|
|
1461
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1462
|
+
const collection = db.collection(collectionName);
|
|
1340
1463
|
const indexes = await collection.indexes();
|
|
1341
1464
|
|
|
1342
1465
|
// Find existing index on the signature field
|
|
@@ -1402,6 +1525,9 @@ export class SimpleMongoHelper {
|
|
|
1402
1525
|
options?: {
|
|
1403
1526
|
session?: ClientSession;
|
|
1404
1527
|
ensureIndex?: boolean;
|
|
1528
|
+
database?: string;
|
|
1529
|
+
ref?: string;
|
|
1530
|
+
type?: string;
|
|
1405
1531
|
}
|
|
1406
1532
|
): Promise<WriteByRefResult> {
|
|
1407
1533
|
this.ensureInitialized();
|
|
@@ -1420,6 +1546,9 @@ export class SimpleMongoHelper {
|
|
|
1420
1546
|
const mode = outputConfig.mode || this.config.output?.mode || 'append';
|
|
1421
1547
|
const ensureIndex = options?.ensureIndex !== false; // Default to true
|
|
1422
1548
|
const session = options?.session;
|
|
1549
|
+
const database = options?.database;
|
|
1550
|
+
const dbRef = options?.ref;
|
|
1551
|
+
const dbType = options?.type;
|
|
1423
1552
|
|
|
1424
1553
|
const result: WriteByRefResult = {
|
|
1425
1554
|
inserted: 0,
|
|
@@ -1429,7 +1558,8 @@ export class SimpleMongoHelper {
|
|
|
1429
1558
|
};
|
|
1430
1559
|
|
|
1431
1560
|
try {
|
|
1432
|
-
const
|
|
1561
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref: dbRef, type: dbType }));
|
|
1562
|
+
const collection = db.collection(collectionName);
|
|
1433
1563
|
|
|
1434
1564
|
// Handle replace mode: clear collection first
|
|
1435
1565
|
if (mode === 'replace') {
|
|
@@ -1595,6 +1725,9 @@ export class SimpleMongoHelper {
|
|
|
1595
1725
|
const writeResult = await this.writeByRef(ref, documents, {
|
|
1596
1726
|
session: options?.session,
|
|
1597
1727
|
ensureIndex: options?.ensureIndex,
|
|
1728
|
+
database: options?.database,
|
|
1729
|
+
ref: options?.ref,
|
|
1730
|
+
type: options?.type,
|
|
1598
1731
|
});
|
|
1599
1732
|
|
|
1600
1733
|
const result: WriteStageResult = {
|
|
@@ -1613,7 +1746,7 @@ export class SimpleMongoHelper {
|
|
|
1613
1746
|
provider: options.complete.provider,
|
|
1614
1747
|
metadata: options.complete.metadata,
|
|
1615
1748
|
},
|
|
1616
|
-
{ session: options.session }
|
|
1749
|
+
{ session: options.session, database: options?.database, ref: options?.ref, type: options?.type }
|
|
1617
1750
|
);
|
|
1618
1751
|
result.completed = true;
|
|
1619
1752
|
} catch (error) {
|
|
@@ -1651,6 +1784,9 @@ export class SimpleMongoHelper {
|
|
|
1651
1784
|
onUnmatched1 = 'include',
|
|
1652
1785
|
onUnmatched2 = 'include',
|
|
1653
1786
|
session,
|
|
1787
|
+
database,
|
|
1788
|
+
ref,
|
|
1789
|
+
type,
|
|
1654
1790
|
} = options;
|
|
1655
1791
|
|
|
1656
1792
|
// Determine join behavior from joinType or fallback to onUnmatched flags
|
|
@@ -1694,9 +1830,10 @@ export class SimpleMongoHelper {
|
|
|
1694
1830
|
};
|
|
1695
1831
|
|
|
1696
1832
|
try {
|
|
1697
|
-
const
|
|
1698
|
-
const
|
|
1699
|
-
const
|
|
1833
|
+
const db = this.getDatabase(this.resolveDatabase({ database, ref, type }));
|
|
1834
|
+
const coll1 = db.collection(sourceCollection1);
|
|
1835
|
+
const coll2 = db.collection(sourceCollection2);
|
|
1836
|
+
const targetColl = db.collection(targetCollection);
|
|
1700
1837
|
|
|
1701
1838
|
const findOptions: any = {};
|
|
1702
1839
|
if (session) {
|
|
@@ -2116,20 +2253,118 @@ export class SimpleMongoHelper {
|
|
|
2116
2253
|
}
|
|
2117
2254
|
|
|
2118
2255
|
/**
|
|
2119
|
-
*
|
|
2120
|
-
* @param connectionString - MongoDB connection string
|
|
2121
|
-
* @returns
|
|
2256
|
+
* Strips database name from MongoDB connection string, returning base connection string.
|
|
2257
|
+
* @param connectionString - MongoDB connection string (may include database name)
|
|
2258
|
+
* @returns Base connection string without database name
|
|
2259
|
+
* @example
|
|
2260
|
+
* stripDatabaseFromConnectionString('mongodb://localhost:27017/admin')
|
|
2261
|
+
* // Returns: 'mongodb://localhost:27017/'
|
|
2122
2262
|
*/
|
|
2123
|
-
private
|
|
2263
|
+
private stripDatabaseFromConnectionString(connectionString: string): string {
|
|
2124
2264
|
try {
|
|
2125
2265
|
const url = new URL(connectionString);
|
|
2126
|
-
|
|
2127
|
-
|
|
2266
|
+
// Remove pathname (database name) but keep trailing slash
|
|
2267
|
+
url.pathname = '/';
|
|
2268
|
+
return url.toString();
|
|
2128
2269
|
} catch {
|
|
2129
|
-
// If URL parsing fails, try
|
|
2130
|
-
|
|
2131
|
-
|
|
2270
|
+
// If URL parsing fails, try regex pattern matching
|
|
2271
|
+
// Match: mongodb://host:port/database?options or mongodb://host:port/database
|
|
2272
|
+
const match = connectionString.match(/^([^\/]+\/\/[^\/]+)\/([^\/\?]+)(\?.*)?$/);
|
|
2273
|
+
if (match) {
|
|
2274
|
+
// Return base URL with trailing slash
|
|
2275
|
+
return match[1] + '/';
|
|
2276
|
+
}
|
|
2277
|
+
// If no database found, return as-is (should already be base URL)
|
|
2278
|
+
return connectionString.endsWith('/') ? connectionString : connectionString + '/';
|
|
2132
2279
|
}
|
|
2133
2280
|
}
|
|
2281
|
+
|
|
2282
|
+
/**
|
|
2283
|
+
* Resolves the database name from provided options using the databases config map.
|
|
2284
|
+
* Priority: database > ref+type > ref > type
|
|
2285
|
+
* @param options - Options containing database, ref, and/or type
|
|
2286
|
+
* @returns Resolved database name or undefined (will default to 'admin')
|
|
2287
|
+
* @throws Error if no match found or multiple matches found
|
|
2288
|
+
* @internal
|
|
2289
|
+
*/
|
|
2290
|
+
private resolveDatabase(options?: { database?: string; ref?: string; type?: string }): string | undefined {
|
|
2291
|
+
// Priority 1: If database is provided directly, use it
|
|
2292
|
+
if (options?.database) {
|
|
2293
|
+
return options.database;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// If no config or no databases map, return undefined (will default to 'admin')
|
|
2297
|
+
if (!this.config || !this.config.databases || this.config.databases.length === 0) {
|
|
2298
|
+
return undefined;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
const databases = this.config.databases;
|
|
2302
|
+
|
|
2303
|
+
// Priority 2: If both ref and type are provided, find exact match
|
|
2304
|
+
if (options?.ref && options?.type) {
|
|
2305
|
+
const matches = databases.filter(db => db.ref === options.ref && db.type === options.type);
|
|
2306
|
+
if (matches.length === 0) {
|
|
2307
|
+
throw new Error(`No database found for ref: ${options.ref} and type: ${options.type}`);
|
|
2308
|
+
}
|
|
2309
|
+
if (matches.length > 1) {
|
|
2310
|
+
throw new Error(`Multiple databases found for ref: ${options.ref} and type: ${options.type}`);
|
|
2311
|
+
}
|
|
2312
|
+
return matches[0].database;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// Priority 3: If ref is provided, find by ref
|
|
2316
|
+
if (options?.ref) {
|
|
2317
|
+
const matches = databases.filter(db => db.ref === options.ref);
|
|
2318
|
+
if (matches.length === 0) {
|
|
2319
|
+
throw new Error(`No database found for ref: ${options.ref}`);
|
|
2320
|
+
}
|
|
2321
|
+
if (matches.length > 1) {
|
|
2322
|
+
throw new Error(`Multiple databases found for ref: ${options.ref}. Use 'type' parameter to narrow down.`);
|
|
2323
|
+
}
|
|
2324
|
+
return matches[0].database;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// Priority 4: If type is provided, find by type
|
|
2328
|
+
if (options?.type) {
|
|
2329
|
+
const matches = databases.filter(db => db.type === options.type);
|
|
2330
|
+
if (matches.length === 0) {
|
|
2331
|
+
throw new Error(`No database found for type: ${options.type}`);
|
|
2332
|
+
}
|
|
2333
|
+
if (matches.length > 1) {
|
|
2334
|
+
throw new Error(`Multiple databases found for type: ${options.type}. Use 'ref' parameter to narrow down.`);
|
|
2335
|
+
}
|
|
2336
|
+
return matches[0].database;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// No options provided, return undefined (will default to 'admin')
|
|
2340
|
+
return undefined;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
/**
|
|
2344
|
+
* Gets a database instance by name. Defaults to 'admin' if no name provided.
|
|
2345
|
+
* @param databaseName - Optional database name (defaults to 'admin')
|
|
2346
|
+
* @returns Database instance
|
|
2347
|
+
* @throws Error if client is not initialized
|
|
2348
|
+
* @internal
|
|
2349
|
+
*/
|
|
2350
|
+
private getDatabase(databaseName?: string): Db {
|
|
2351
|
+
if (!this.client) {
|
|
2352
|
+
throw new Error('MongoDB client not initialized. Call initialize() first.');
|
|
2353
|
+
}
|
|
2354
|
+
return this.client.db(databaseName || 'admin');
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
/**
|
|
2358
|
+
* Gets a database instance by name. Defaults to 'admin' if no name provided.
|
|
2359
|
+
* Public method for use by ProgressAPIImpl.
|
|
2360
|
+
* @param databaseName - Optional database name (defaults to 'admin')
|
|
2361
|
+
* @param ref - Optional ref for database resolution
|
|
2362
|
+
* @param type - Optional type for database resolution
|
|
2363
|
+
* @returns Database instance
|
|
2364
|
+
* @throws Error if client is not initialized
|
|
2365
|
+
*/
|
|
2366
|
+
public getDatabaseByName(databaseName?: string, ref?: string, type?: string): Db {
|
|
2367
|
+
return this.getDatabase(this.resolveDatabase({ database: databaseName, ref, type }));
|
|
2368
|
+
}
|
|
2134
2369
|
}
|
|
2135
2370
|
|