opticedge-cloud-utils 1.0.13 → 1.0.15
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/dist/db/mongo2.d.ts +4 -0
- package/dist/db/mongo2.js +30 -0
- package/dist/db/mongo2.test.d.ts +1 -0
- package/dist/db/mongo2.test.js +86 -0
- package/dist/db/mongo3.d.ts +6 -0
- package/dist/db/mongo3.js +39 -0
- package/dist/db/mongo3.test.d.ts +1 -0
- package/dist/db/mongo3.test.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types/pokemontcg.d.ts +1 -1
- package/package.json +1 -1
- package/src/db/mongo2.test.ts +63 -0
- package/src/db/mongo2.ts +32 -0
- package/src/db/mongo3.test.ts +100 -0
- package/src/db/mongo3.ts +47 -0
- package/src/index.ts +2 -0
- package/src/types/pokemontcg.ts +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Db, Collection, Document } from 'mongodb';
|
|
2
|
+
export declare function connectToMongo2(projectId: string, uriSecret: string): Promise<void>;
|
|
3
|
+
export declare function getDb2(dbName: string): Db;
|
|
4
|
+
export declare function getCollection2<T extends Document = Document>(dbName: string, collectionName: string): Collection<T>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.connectToMongo2 = connectToMongo2;
|
|
4
|
+
exports.getDb2 = getDb2;
|
|
5
|
+
exports.getCollection2 = getCollection2;
|
|
6
|
+
// Multi DB safe util
|
|
7
|
+
const mongodb_1 = require("mongodb");
|
|
8
|
+
const secrets_1 = require("../utils/secrets");
|
|
9
|
+
let client;
|
|
10
|
+
let connectPromise;
|
|
11
|
+
async function connectToMongo2(projectId, uriSecret) {
|
|
12
|
+
if (client)
|
|
13
|
+
return; // already connected
|
|
14
|
+
if (!connectPromise) {
|
|
15
|
+
connectPromise = (async () => {
|
|
16
|
+
const uri = await (0, secrets_1.getSecret)(projectId, uriSecret);
|
|
17
|
+
client = new mongodb_1.MongoClient(uri);
|
|
18
|
+
await client.connect();
|
|
19
|
+
})();
|
|
20
|
+
}
|
|
21
|
+
await connectPromise;
|
|
22
|
+
}
|
|
23
|
+
function getDb2(dbName) {
|
|
24
|
+
if (!client)
|
|
25
|
+
throw new Error('Mongo not initialized');
|
|
26
|
+
return client.db(dbName);
|
|
27
|
+
}
|
|
28
|
+
function getCollection2(dbName, collectionName) {
|
|
29
|
+
return getDb2(dbName).collection(collectionName);
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const mongo2_1 = require("./mongo2");
|
|
37
|
+
const mongodb_1 = require("mongodb");
|
|
38
|
+
const secretsModule = __importStar(require("../utils/secrets"));
|
|
39
|
+
jest.mock('mongodb');
|
|
40
|
+
jest.mock('../utils/secrets');
|
|
41
|
+
const mockDb = {
|
|
42
|
+
collection: jest.fn()
|
|
43
|
+
};
|
|
44
|
+
const mockConnect = jest.fn();
|
|
45
|
+
const mockClient = {
|
|
46
|
+
connect: mockConnect,
|
|
47
|
+
db: jest.fn().mockReturnValue(mockDb)
|
|
48
|
+
};
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
secretsModule.getSecret.mockResolvedValue('mongodb://mock-uri');
|
|
52
|
+
mongodb_1.MongoClient.mockImplementation(() => mockClient);
|
|
53
|
+
});
|
|
54
|
+
describe('Mongo Utils', () => {
|
|
55
|
+
it('should connect to MongoDB and store client', async () => {
|
|
56
|
+
await (0, mongo2_1.connectToMongo2)('test-project', 'mongo-uri-secret');
|
|
57
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', 'mongo-uri-secret');
|
|
58
|
+
expect(mockConnect).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
it('should return a Db instance when client is initialized', async () => {
|
|
61
|
+
await (0, mongo2_1.connectToMongo2)('test-project', 'mongo-uri-secret');
|
|
62
|
+
const db = (0, mongo2_1.getDb2)('test-db');
|
|
63
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db');
|
|
64
|
+
expect(db).toBe(mockDb);
|
|
65
|
+
});
|
|
66
|
+
it('should return a collection when client is initialized', async () => {
|
|
67
|
+
await (0, mongo2_1.connectToMongo2)('test-project', 'mongo-uri-secret');
|
|
68
|
+
(0, mongo2_1.getCollection2)('test-db', 'users');
|
|
69
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db');
|
|
70
|
+
expect(mockDb.collection).toHaveBeenCalledWith('users');
|
|
71
|
+
});
|
|
72
|
+
it('should throw error if client is not initialized (getDb)', () => {
|
|
73
|
+
jest.isolateModules(() => {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
75
|
+
const { getDb2 } = require('./mongo2');
|
|
76
|
+
expect(() => getDb2('test-db')).toThrow('Mongo not initialized');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('should throw error if client is not initialized (getCollection)', () => {
|
|
80
|
+
jest.isolateModules(() => {
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
82
|
+
const { getCollection2 } = require('./mongo2');
|
|
83
|
+
expect(() => getCollection2('test-db', 'users')).toThrow('Mongo not initialized');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { MongoClient, Db, Collection, Document } from 'mongodb';
|
|
2
|
+
export declare function connectToMongo3(projectId: string, uriSecret: string): Promise<MongoClient>;
|
|
3
|
+
export declare function getDb3(uriSecret: string, dbName: string): Db;
|
|
4
|
+
export declare function getCollection3<T extends Document = Document>(uriSecret: string, dbName: string, collectionName: string): Collection<T>;
|
|
5
|
+
export declare const __mongoClients: Map<string, MongoClient>;
|
|
6
|
+
export declare const __connectPromises: Map<string, Promise<void>>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.__connectPromises = exports.__mongoClients = void 0;
|
|
4
|
+
exports.connectToMongo3 = connectToMongo3;
|
|
5
|
+
exports.getDb3 = getDb3;
|
|
6
|
+
exports.getCollection3 = getCollection3;
|
|
7
|
+
// Multi cluster safe util
|
|
8
|
+
const mongodb_1 = require("mongodb");
|
|
9
|
+
const secrets_1 = require("../utils/secrets");
|
|
10
|
+
const clients = new Map();
|
|
11
|
+
const connectPromises = new Map();
|
|
12
|
+
async function connectToMongo3(projectId, uriSecret) {
|
|
13
|
+
if (clients.has(uriSecret)) {
|
|
14
|
+
return clients.get(uriSecret);
|
|
15
|
+
}
|
|
16
|
+
if (!connectPromises.has(uriSecret)) {
|
|
17
|
+
const promise = (async () => {
|
|
18
|
+
const uri = await (0, secrets_1.getSecret)(projectId, uriSecret);
|
|
19
|
+
const client = new mongodb_1.MongoClient(uri);
|
|
20
|
+
await client.connect();
|
|
21
|
+
clients.set(uriSecret, client);
|
|
22
|
+
})();
|
|
23
|
+
connectPromises.set(uriSecret, promise);
|
|
24
|
+
}
|
|
25
|
+
await connectPromises.get(uriSecret);
|
|
26
|
+
return clients.get(uriSecret);
|
|
27
|
+
}
|
|
28
|
+
function getDb3(uriSecret, dbName) {
|
|
29
|
+
const client = clients.get(uriSecret);
|
|
30
|
+
if (!client) {
|
|
31
|
+
throw new Error(`Mongo client for secret "${uriSecret}" not initialized`);
|
|
32
|
+
}
|
|
33
|
+
return client.db(dbName);
|
|
34
|
+
}
|
|
35
|
+
function getCollection3(uriSecret, dbName, collectionName) {
|
|
36
|
+
return getDb3(uriSecret, dbName).collection(collectionName);
|
|
37
|
+
}
|
|
38
|
+
exports.__mongoClients = clients;
|
|
39
|
+
exports.__connectPromises = connectPromises;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const mongo3_1 = require("./mongo3");
|
|
37
|
+
const mongodb_1 = require("mongodb");
|
|
38
|
+
const secretsModule = __importStar(require("../utils/secrets"));
|
|
39
|
+
jest.mock('mongodb');
|
|
40
|
+
jest.mock('../utils/secrets');
|
|
41
|
+
const mockDb = {
|
|
42
|
+
collection: jest.fn()
|
|
43
|
+
};
|
|
44
|
+
const mockConnect = jest.fn();
|
|
45
|
+
const mockClient = {
|
|
46
|
+
connect: mockConnect,
|
|
47
|
+
db: jest.fn().mockReturnValue(mockDb)
|
|
48
|
+
};
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
mongo3_1.__mongoClients.clear();
|
|
52
|
+
mongo3_1.__connectPromises.clear();
|
|
53
|
+
secretsModule.getSecret.mockResolvedValue('mongodb://mock-uri');
|
|
54
|
+
mongodb_1.MongoClient.mockImplementation(() => mockClient);
|
|
55
|
+
});
|
|
56
|
+
describe('MongoClientManager (multi-cluster)', () => {
|
|
57
|
+
const clusterASecret = 'clusterA-uri-secret';
|
|
58
|
+
const clusterBSecret = 'clusterB-uri-secret';
|
|
59
|
+
it('should connect and cache client for a single cluster', async () => {
|
|
60
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterASecret);
|
|
61
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterASecret);
|
|
62
|
+
expect(mockConnect).toHaveBeenCalled();
|
|
63
|
+
const db = (0, mongo3_1.getDb3)(clusterASecret, 'test-db');
|
|
64
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db');
|
|
65
|
+
expect(db).toBe(mockDb);
|
|
66
|
+
});
|
|
67
|
+
it('should reuse client if already connected', async () => {
|
|
68
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterASecret);
|
|
69
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterASecret);
|
|
70
|
+
// getSecret and connect should only happen once
|
|
71
|
+
expect(secretsModule.getSecret).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(mockConnect).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
74
|
+
it('should connect and cache separate clients for multiple clusters', async () => {
|
|
75
|
+
// Mock different URIs for 2 clusters
|
|
76
|
+
;
|
|
77
|
+
secretsModule.getSecret
|
|
78
|
+
.mockResolvedValueOnce('mongodb://mock-uri-A')
|
|
79
|
+
.mockResolvedValueOnce('mongodb://mock-uri-B');
|
|
80
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterASecret);
|
|
81
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterBSecret);
|
|
82
|
+
// Should call getSecret + connect twice (for 2 clusters)
|
|
83
|
+
expect(secretsModule.getSecret).toHaveBeenCalledTimes(2);
|
|
84
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterASecret);
|
|
85
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterBSecret);
|
|
86
|
+
expect(mockConnect).toHaveBeenCalledTimes(2);
|
|
87
|
+
});
|
|
88
|
+
it('should return a collection when client is initialized', async () => {
|
|
89
|
+
await (0, mongo3_1.connectToMongo3)('test-project', clusterASecret);
|
|
90
|
+
(0, mongo3_1.getCollection3)(clusterASecret, 'test-db', 'users');
|
|
91
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db');
|
|
92
|
+
expect(mockDb.collection).toHaveBeenCalledWith('users');
|
|
93
|
+
});
|
|
94
|
+
it('should throw error if client not initialized (getDb)', () => {
|
|
95
|
+
jest.isolateModules(() => {
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
97
|
+
const { getDb3 } = require('./mongo3');
|
|
98
|
+
expect(() => getDb3('unknown-uri-secret', 'some-db')).toThrow('Mongo client for secret "unknown-uri-secret" not initialized');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it('should throw error if client not initialized (getCollection)', () => {
|
|
102
|
+
jest.isolateModules(() => {
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
104
|
+
const { getCollection3 } = require('./mongo3');
|
|
105
|
+
expect(() => getCollection3('unknown-uri-secret', 'some-db', 'users')).toThrow('Mongo client for secret "unknown-uri-secret" not initialized');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./auth/verify"), exports);
|
|
18
18
|
__exportStar(require("./db/mongo"), exports);
|
|
19
|
+
__exportStar(require("./db/mongo2"), exports);
|
|
20
|
+
__exportStar(require("./db/mongo3"), exports);
|
|
19
21
|
__exportStar(require("./types/pokemontcg"), exports);
|
|
20
22
|
__exportStar(require("./utils/env"), exports);
|
|
21
23
|
__exportStar(require("./utils/secrets"), exports);
|
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { connectToMongo2, getDb2, getCollection2 } from './mongo2'
|
|
2
|
+
import { MongoClient, Db } from 'mongodb'
|
|
3
|
+
import * as secretsModule from '../utils/secrets'
|
|
4
|
+
|
|
5
|
+
jest.mock('mongodb')
|
|
6
|
+
jest.mock('../utils/secrets')
|
|
7
|
+
|
|
8
|
+
const mockDb = {
|
|
9
|
+
collection: jest.fn()
|
|
10
|
+
} as unknown as Db
|
|
11
|
+
|
|
12
|
+
const mockConnect = jest.fn()
|
|
13
|
+
const mockClient = {
|
|
14
|
+
connect: mockConnect,
|
|
15
|
+
db: jest.fn().mockReturnValue(mockDb)
|
|
16
|
+
} as unknown as MongoClient
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks()
|
|
20
|
+
;(secretsModule.getSecret as jest.Mock).mockResolvedValue('mongodb://mock-uri')
|
|
21
|
+
;(MongoClient as unknown as jest.Mock).mockImplementation(() => mockClient)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('Mongo Utils', () => {
|
|
25
|
+
it('should connect to MongoDB and store client', async () => {
|
|
26
|
+
await connectToMongo2('test-project', 'mongo-uri-secret')
|
|
27
|
+
|
|
28
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', 'mongo-uri-secret')
|
|
29
|
+
expect(mockConnect).toHaveBeenCalled()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should return a Db instance when client is initialized', async () => {
|
|
33
|
+
await connectToMongo2('test-project', 'mongo-uri-secret')
|
|
34
|
+
|
|
35
|
+
const db = getDb2('test-db')
|
|
36
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db')
|
|
37
|
+
expect(db).toBe(mockDb)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should return a collection when client is initialized', async () => {
|
|
41
|
+
await connectToMongo2('test-project', 'mongo-uri-secret')
|
|
42
|
+
|
|
43
|
+
getCollection2('test-db', 'users')
|
|
44
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db')
|
|
45
|
+
expect(mockDb.collection).toHaveBeenCalledWith('users')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should throw error if client is not initialized (getDb)', () => {
|
|
49
|
+
jest.isolateModules(() => {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
51
|
+
const { getDb2 } = require('./mongo2')
|
|
52
|
+
expect(() => getDb2('test-db')).toThrow('Mongo not initialized')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should throw error if client is not initialized (getCollection)', () => {
|
|
57
|
+
jest.isolateModules(() => {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
59
|
+
const { getCollection2 } = require('./mongo2')
|
|
60
|
+
expect(() => getCollection2('test-db', 'users')).toThrow('Mongo not initialized')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
})
|
package/src/db/mongo2.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Multi DB safe util
|
|
2
|
+
import { MongoClient, Db, Collection, Document } from 'mongodb'
|
|
3
|
+
import { getSecret } from '../utils/secrets'
|
|
4
|
+
|
|
5
|
+
let client: MongoClient
|
|
6
|
+
let connectPromise: Promise<void> | undefined
|
|
7
|
+
|
|
8
|
+
export async function connectToMongo2(projectId: string, uriSecret: string) {
|
|
9
|
+
if (client) return // already connected
|
|
10
|
+
|
|
11
|
+
if (!connectPromise) {
|
|
12
|
+
connectPromise = (async () => {
|
|
13
|
+
const uri = await getSecret(projectId, uriSecret)
|
|
14
|
+
client = new MongoClient(uri)
|
|
15
|
+
await client.connect()
|
|
16
|
+
})()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await connectPromise
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getDb2(dbName: string): Db {
|
|
23
|
+
if (!client) throw new Error('Mongo not initialized')
|
|
24
|
+
return client.db(dbName)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getCollection2<T extends Document = Document>(
|
|
28
|
+
dbName: string,
|
|
29
|
+
collectionName: string
|
|
30
|
+
): Collection<T> {
|
|
31
|
+
return getDb2(dbName).collection<T>(collectionName)
|
|
32
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
connectToMongo3,
|
|
3
|
+
getDb3,
|
|
4
|
+
getCollection3,
|
|
5
|
+
__mongoClients,
|
|
6
|
+
__connectPromises
|
|
7
|
+
} from './mongo3'
|
|
8
|
+
import { MongoClient, Db } from 'mongodb'
|
|
9
|
+
import * as secretsModule from '../utils/secrets'
|
|
10
|
+
|
|
11
|
+
jest.mock('mongodb')
|
|
12
|
+
jest.mock('../utils/secrets')
|
|
13
|
+
|
|
14
|
+
const mockDb = {
|
|
15
|
+
collection: jest.fn()
|
|
16
|
+
} as unknown as Db
|
|
17
|
+
|
|
18
|
+
const mockConnect = jest.fn()
|
|
19
|
+
const mockClient = {
|
|
20
|
+
connect: mockConnect,
|
|
21
|
+
db: jest.fn().mockReturnValue(mockDb)
|
|
22
|
+
} as unknown as MongoClient
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks()
|
|
26
|
+
__mongoClients.clear()
|
|
27
|
+
__connectPromises.clear()
|
|
28
|
+
;(secretsModule.getSecret as jest.Mock).mockResolvedValue('mongodb://mock-uri')
|
|
29
|
+
;(MongoClient as unknown as jest.Mock).mockImplementation(() => mockClient)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('MongoClientManager (multi-cluster)', () => {
|
|
33
|
+
const clusterASecret = 'clusterA-uri-secret'
|
|
34
|
+
const clusterBSecret = 'clusterB-uri-secret'
|
|
35
|
+
|
|
36
|
+
it('should connect and cache client for a single cluster', async () => {
|
|
37
|
+
await connectToMongo3('test-project', clusterASecret)
|
|
38
|
+
|
|
39
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterASecret)
|
|
40
|
+
expect(mockConnect).toHaveBeenCalled()
|
|
41
|
+
|
|
42
|
+
const db = getDb3(clusterASecret, 'test-db')
|
|
43
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db')
|
|
44
|
+
expect(db).toBe(mockDb)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should reuse client if already connected', async () => {
|
|
48
|
+
await connectToMongo3('test-project', clusterASecret)
|
|
49
|
+
await connectToMongo3('test-project', clusterASecret)
|
|
50
|
+
|
|
51
|
+
// getSecret and connect should only happen once
|
|
52
|
+
expect(secretsModule.getSecret).toHaveBeenCalledTimes(1)
|
|
53
|
+
expect(mockConnect).toHaveBeenCalledTimes(1)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should connect and cache separate clients for multiple clusters', async () => {
|
|
57
|
+
// Mock different URIs for 2 clusters
|
|
58
|
+
;(secretsModule.getSecret as jest.Mock)
|
|
59
|
+
.mockResolvedValueOnce('mongodb://mock-uri-A')
|
|
60
|
+
.mockResolvedValueOnce('mongodb://mock-uri-B')
|
|
61
|
+
|
|
62
|
+
await connectToMongo3('test-project', clusterASecret)
|
|
63
|
+
await connectToMongo3('test-project', clusterBSecret)
|
|
64
|
+
|
|
65
|
+
// Should call getSecret + connect twice (for 2 clusters)
|
|
66
|
+
expect(secretsModule.getSecret).toHaveBeenCalledTimes(2)
|
|
67
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterASecret)
|
|
68
|
+
expect(secretsModule.getSecret).toHaveBeenCalledWith('test-project', clusterBSecret)
|
|
69
|
+
expect(mockConnect).toHaveBeenCalledTimes(2)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should return a collection when client is initialized', async () => {
|
|
73
|
+
await connectToMongo3('test-project', clusterASecret)
|
|
74
|
+
|
|
75
|
+
getCollection3(clusterASecret, 'test-db', 'users')
|
|
76
|
+
|
|
77
|
+
expect(mockClient.db).toHaveBeenCalledWith('test-db')
|
|
78
|
+
expect(mockDb.collection).toHaveBeenCalledWith('users')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should throw error if client not initialized (getDb)', () => {
|
|
82
|
+
jest.isolateModules(() => {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
84
|
+
const { getDb3 } = require('./mongo3')
|
|
85
|
+
expect(() => getDb3('unknown-uri-secret', 'some-db')).toThrow(
|
|
86
|
+
'Mongo client for secret "unknown-uri-secret" not initialized'
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should throw error if client not initialized (getCollection)', () => {
|
|
92
|
+
jest.isolateModules(() => {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
94
|
+
const { getCollection3 } = require('./mongo3')
|
|
95
|
+
expect(() => getCollection3('unknown-uri-secret', 'some-db', 'users')).toThrow(
|
|
96
|
+
'Mongo client for secret "unknown-uri-secret" not initialized'
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
package/src/db/mongo3.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Multi cluster safe util
|
|
2
|
+
import { MongoClient, Db, Collection, Document } from 'mongodb'
|
|
3
|
+
import { getSecret } from '../utils/secrets'
|
|
4
|
+
|
|
5
|
+
const clients: Map<string, MongoClient> = new Map()
|
|
6
|
+
const connectPromises: Map<string, Promise<void>> = new Map()
|
|
7
|
+
|
|
8
|
+
export async function connectToMongo3(projectId: string, uriSecret: string): Promise<MongoClient> {
|
|
9
|
+
if (clients.has(uriSecret)) {
|
|
10
|
+
return clients.get(uriSecret)!
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!connectPromises.has(uriSecret)) {
|
|
14
|
+
const promise = (async () => {
|
|
15
|
+
const uri = await getSecret(projectId, uriSecret)
|
|
16
|
+
const client = new MongoClient(uri)
|
|
17
|
+
await client.connect()
|
|
18
|
+
clients.set(uriSecret, client)
|
|
19
|
+
})()
|
|
20
|
+
|
|
21
|
+
connectPromises.set(uriSecret, promise)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await connectPromises.get(uriSecret)!
|
|
25
|
+
|
|
26
|
+
return clients.get(uriSecret)!
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getDb3(uriSecret: string, dbName: string): Db {
|
|
30
|
+
const client = clients.get(uriSecret)
|
|
31
|
+
if (!client) {
|
|
32
|
+
throw new Error(`Mongo client for secret "${uriSecret}" not initialized`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return client.db(dbName)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getCollection3<T extends Document = Document>(
|
|
39
|
+
uriSecret: string,
|
|
40
|
+
dbName: string,
|
|
41
|
+
collectionName: string
|
|
42
|
+
): Collection<T> {
|
|
43
|
+
return getDb3(uriSecret, dbName).collection<T>(collectionName)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const __mongoClients = clients
|
|
47
|
+
export const __connectPromises = connectPromises
|
package/src/index.ts
CHANGED