@pioneer-platform/mongo-atlas 1.0.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 ADDED
@@ -0,0 +1,70 @@
1
+ # MongoDB Atlas Connection Manager
2
+
3
+ A modern, robust connection manager for MongoDB Atlas specifically designed for the Pioneer platform.
4
+
5
+ ## Features
6
+
7
+ - Proper connection pooling and reuse
8
+ - Support for MongoDB Atlas connection strings
9
+ - Automatic reconnection
10
+ - Connection caching
11
+ - Monk-like API for backward compatibility
12
+ - MongoDB native driver for optimal performance
13
+
14
+ ## Usage
15
+
16
+ ```javascript
17
+ const mongo = require('@pioneer-platform/mongo-atlas');
18
+
19
+ // Get a collection from a specific database
20
+ async function getTickets() {
21
+ const ticketsCollection = await mongo.get('tickets', 'support');
22
+ return ticketsCollection.find({ status: 'open' });
23
+ }
24
+
25
+ // Use the MongoDB native collection
26
+ async function advancedQuery() {
27
+ const collection = await mongo.get('users', 'admin');
28
+ return collection.collection.aggregate([
29
+ { $match: { active: true } },
30
+ { $group: { _id: '$role', count: { $sum: 1 } } }
31
+ ]).toArray();
32
+ }
33
+ ```
34
+
35
+ ## Environment Variables
36
+
37
+ - `MONGO_CONNECTION`: MongoDB connection string (required)
38
+ - `MONGO_DEFAULT_DB`: Default database name (optional, defaults to 'pioneer')
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install @pioneer-platform/mongo-atlas
44
+ ```
45
+
46
+ ## Migrating from @pioneer-platform/default-mongo
47
+
48
+ ### Before:
49
+ ```javascript
50
+ const connection = require('@pioneer-platform/default-mongo');
51
+ const ticketsDb = connection.get('tickets');
52
+
53
+ // Using ticketsDb
54
+ const tickets = await ticketsDb.find({ status: 'open' });
55
+ ```
56
+
57
+ ### After:
58
+ ```javascript
59
+ const mongo = require('@pioneer-platform/mongo-atlas');
60
+
61
+ // Make sure to await the get method as it's now async
62
+ const ticketsDb = await mongo.get('tickets', 'support');
63
+
64
+ // The same methods are available
65
+ const tickets = await ticketsDb.find({ status: 'open' });
66
+ ```
67
+
68
+ ## License
69
+
70
+ MIT
package/index.js ADDED
@@ -0,0 +1,145 @@
1
+ /*
2
+ * MongoDB Atlas Connection Manager
3
+ * Modern, robust connection handling for MongoDB Atlas
4
+ */
5
+
6
+ const { MongoClient } = require('mongodb');
7
+ const debug = require('debug')('mongo-atlas');
8
+
9
+ // Connection cache to avoid multiple connections to the same DB
10
+ const connectionCache = {
11
+ clients: {},
12
+ databases: {}
13
+ };
14
+
15
+ // Default options for MongoDB connections
16
+ const defaultOptions = {
17
+ useUnifiedTopology: true,
18
+ useNewUrlParser: true,
19
+ connectTimeoutMS: 10000,
20
+ socketTimeoutMS: 45000,
21
+ serverSelectionTimeoutMS: 15000,
22
+ maxPoolSize: 20,
23
+ minPoolSize: 5
24
+ };
25
+
26
+ // Get or create a MongoClient for a connection string
27
+ async function getOrCreateClient(connectionString, options = {}) {
28
+ // Use original connection string as the cache key
29
+ if (!connectionCache.clients[connectionString]) {
30
+ debug(`Creating new MongoClient for ${connectionString}`);
31
+ const client = new MongoClient(connectionString, { ...defaultOptions, ...options });
32
+ await client.connect();
33
+ connectionCache.clients[connectionString] = client;
34
+
35
+ // Set up automatic reconnection
36
+ client.on('close', () => {
37
+ debug(`Connection to ${connectionString} closed, will reconnect on next use`);
38
+ delete connectionCache.clients[connectionString];
39
+ });
40
+ }
41
+
42
+ return connectionCache.clients[connectionString];
43
+ }
44
+
45
+ // Main connection object
46
+ const connection = {
47
+ /**
48
+ * Get a collection from a specific database
49
+ * @param {string} collection - Collection name
50
+ * @param {string} dbName - Database name (defaults to env variable or 'pioneer')
51
+ * @param {object} options - MongoDB connection options
52
+ * @returns {object} - MongoDB collection with added helper methods
53
+ */
54
+ async get(collection, dbName, options = {}) {
55
+ const connectionString = process.env.MONGO_CONNECTION || 'mongodb://localhost:27017/pioneer';
56
+ const targetDb = dbName || process.env.MONGO_DEFAULT_DB || 'pioneer';
57
+
58
+ debug(`Getting collection ${collection} from database ${targetDb}`);
59
+
60
+ try {
61
+ const client = await getOrCreateClient(connectionString, options);
62
+ const db = client.db(targetDb);
63
+ const mongoCollection = db.collection(collection);
64
+
65
+ // Cache the database connection
66
+ const cacheKey = `${connectionString}:${targetDb}`;
67
+ connectionCache.databases[cacheKey] = db;
68
+
69
+ // Enhance the collection with useful methods
70
+ return enhanceCollection(mongoCollection);
71
+ } catch (error) {
72
+ debug(`Error getting collection ${collection}: ${error.message}`);
73
+ throw error;
74
+ }
75
+ },
76
+
77
+ /**
78
+ * Get a database instance
79
+ * @param {string} dbName - Database name
80
+ * @returns {object} - MongoDB database instance
81
+ */
82
+ async getDb(dbName) {
83
+ const connectionString = process.env.MONGO_CONNECTION || 'mongodb://localhost:27017/pioneer';
84
+ const targetDb = dbName || process.env.MONGO_DEFAULT_DB || 'pioneer';
85
+
86
+ debug(`Getting database ${targetDb}`);
87
+
88
+ try {
89
+ const client = await getOrCreateClient(connectionString);
90
+ return client.db(targetDb);
91
+ } catch (error) {
92
+ debug(`Error getting database ${targetDb}: ${error.message}`);
93
+ throw error;
94
+ }
95
+ },
96
+
97
+ /**
98
+ * Close all connections
99
+ */
100
+ async close() {
101
+ debug('Closing all connections');
102
+
103
+ const clients = Object.values(connectionCache.clients);
104
+ for (const client of clients) {
105
+ await client.close();
106
+ }
107
+
108
+ connectionCache.clients = {};
109
+ connectionCache.databases = {};
110
+ }
111
+ };
112
+
113
+ // Add Monk-like methods to a MongoDB collection
114
+ function enhanceCollection(collection) {
115
+ return {
116
+ // Original collection
117
+ collection,
118
+
119
+ // Monk-like methods for backward compatibility
120
+ find: (query = {}, options = {}) => collection.find(query, options).toArray(),
121
+ findOne: (query = {}, options = {}) => collection.findOne(query, options),
122
+ insert: (doc, options = {}) => collection.insertOne(doc, options),
123
+ update: (query, update, options = {}) => collection.updateOne(query, update, options),
124
+ findOneAndUpdate: (query, update, options = {}) => collection.findOneAndUpdate(query, update, { returnDocument: 'after', ...options }),
125
+ remove: (query, options = {}) => collection.deleteOne(query, options),
126
+ removeMany: (query, options = {}) => collection.deleteMany(query, options),
127
+ count: (query = {}) => collection.countDocuments(query),
128
+ distinct: (field, query = {}) => collection.distinct(field, query)
129
+ };
130
+ }
131
+
132
+ // Handle process termination
133
+ process.on('SIGINT', async () => {
134
+ debug('Received SIGINT, closing connections');
135
+ await connection.close();
136
+ process.exit(0);
137
+ });
138
+
139
+ process.on('SIGTERM', async () => {
140
+ debug('Received SIGTERM, closing connections');
141
+ await connection.close();
142
+ process.exit(0);
143
+ });
144
+
145
+ module.exports = connection;
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@pioneer-platform/mongo-atlas",
3
+ "version": "1.0.0",
4
+ "description": "Modern MongoDB Atlas connection manager for Pioneer platform",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "jest"
8
+ },
9
+ "keywords": [
10
+ "mongodb",
11
+ "atlas",
12
+ "database",
13
+ "connection",
14
+ "pioneer"
15
+ ],
16
+ "author": "Pioneer Team",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "mongodb": "^4.12.0",
20
+ "debug": "^4.3.4"
21
+ },
22
+ "devDependencies": {
23
+ "jest": "^29.2.2"
24
+ },
25
+ "directories": {
26
+ "test": "tests"
27
+ }
28
+ }
@@ -0,0 +1,134 @@
1
+ const mongodb = require('mongodb');
2
+ const connection = require('../index');
3
+
4
+ // Mock MongoDB client
5
+ jest.mock('mongodb', () => {
6
+ const mockCollection = {
7
+ find: jest.fn().mockReturnThis(),
8
+ toArray: jest.fn().mockResolvedValue([]),
9
+ findOne: jest.fn().mockResolvedValue({}),
10
+ insertOne: jest.fn().mockResolvedValue({ insertedId: 'mock-id' }),
11
+ updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
12
+ findOneAndUpdate: jest.fn().mockResolvedValue({ value: {} }),
13
+ deleteOne: jest.fn().mockResolvedValue({ deletedCount: 1 }),
14
+ deleteMany: jest.fn().mockResolvedValue({ deletedCount: 5 }),
15
+ countDocuments: jest.fn().mockResolvedValue(10),
16
+ distinct: jest.fn().mockResolvedValue(['value1', 'value2']),
17
+ aggregate: jest.fn().mockReturnThis(),
18
+ };
19
+
20
+ const mockDb = {
21
+ collection: jest.fn().mockReturnValue(mockCollection),
22
+ };
23
+
24
+ const mockClient = {
25
+ connect: jest.fn().mockResolvedValue(this),
26
+ db: jest.fn().mockReturnValue(mockDb),
27
+ close: jest.fn().mockResolvedValue(true),
28
+ on: jest.fn(),
29
+ };
30
+
31
+ return {
32
+ MongoClient: jest.fn().mockImplementation(() => mockClient),
33
+ };
34
+ });
35
+
36
+ describe('MongoDB Atlas Connection Manager', () => {
37
+ beforeEach(() => {
38
+ // Reset mocks
39
+ jest.clearAllMocks();
40
+
41
+ // Set environment variables for testing
42
+ process.env.MONGO_CONNECTION = 'mongodb://localhost:27017/test';
43
+ });
44
+
45
+ afterEach(() => {
46
+ // Clean up
47
+ delete process.env.MONGO_CONNECTION;
48
+ delete process.env.MONGO_DEFAULT_DB;
49
+ });
50
+
51
+ describe('get method', () => {
52
+ it('should get a collection from the specified database', async () => {
53
+ const collection = await connection.get('tickets', 'support');
54
+
55
+ // Verify MongoClient was created with correct connection string
56
+ expect(mongodb.MongoClient).toHaveBeenCalledWith(
57
+ 'mongodb://localhost:27017/test',
58
+ expect.objectContaining({
59
+ connectTimeoutMS: 10000,
60
+ socketTimeoutMS: 45000,
61
+ })
62
+ );
63
+
64
+ // Verify db was called with correct name
65
+ const mockClientInstance = mongodb.MongoClient.mock.results[0].value;
66
+ expect(mockClientInstance.db).toHaveBeenCalledWith('support');
67
+
68
+ // Verify collection was requested
69
+ const mockDbInstance = mockClientInstance.db.mock.results[0].value;
70
+ expect(mockDbInstance.collection).toHaveBeenCalledWith('tickets');
71
+
72
+ // Verify enhanced methods exist
73
+ expect(collection.findOne).toBeDefined();
74
+ expect(collection.find).toBeDefined();
75
+ expect(collection.insert).toBeDefined();
76
+ expect(collection.update).toBeDefined();
77
+ expect(collection.findOneAndUpdate).toBeDefined();
78
+ expect(collection.remove).toBeDefined();
79
+ expect(collection.removeMany).toBeDefined();
80
+ expect(collection.count).toBeDefined();
81
+ expect(collection.distinct).toBeDefined();
82
+ });
83
+
84
+ it('should use default database when dbName is not specified', async () => {
85
+ process.env.MONGO_DEFAULT_DB = 'default-db';
86
+
87
+ await connection.get('users');
88
+
89
+ // Verify db was called with default DB name
90
+ const mockClientInstance = mongodb.MongoClient.mock.results[0].value;
91
+ expect(mockClientInstance.db).toHaveBeenCalledWith('default-db');
92
+ });
93
+
94
+ it('should reuse existing client connection', async () => {
95
+ // Get two collections
96
+ await connection.get('tickets', 'support');
97
+ await connection.get('users', 'admin');
98
+
99
+ // MongoDB client should only be created once
100
+ expect(mongodb.MongoClient).toHaveBeenCalledTimes(1);
101
+
102
+ // But db should be called twice with different names
103
+ const mockClientInstance = mongodb.MongoClient.mock.results[0].value;
104
+ expect(mockClientInstance.db).toHaveBeenCalledTimes(2);
105
+ expect(mockClientInstance.db).toHaveBeenNthCalledWith(1, 'support');
106
+ expect(mockClientInstance.db).toHaveBeenNthCalledWith(2, 'admin');
107
+ });
108
+ });
109
+
110
+ describe('getDb method', () => {
111
+ it('should get a database instance', async () => {
112
+ const db = await connection.getDb('custom-db');
113
+
114
+ // Verify db was called with correct name
115
+ const mockClientInstance = mongodb.MongoClient.mock.results[0].value;
116
+ expect(mockClientInstance.db).toHaveBeenCalledWith('custom-db');
117
+ expect(db).toBeDefined();
118
+ });
119
+ });
120
+
121
+ describe('close method', () => {
122
+ it('should close all connections', async () => {
123
+ // Create a connection first
124
+ await connection.get('tickets', 'support');
125
+
126
+ // Close connections
127
+ await connection.close();
128
+
129
+ // Verify close was called
130
+ const mockClientInstance = mongodb.MongoClient.mock.results[0].value;
131
+ expect(mockClientInstance.close).toHaveBeenCalled();
132
+ });
133
+ });
134
+ });