@pioneer-platform/default-mongo-v2 1.0.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/CHANGELOG.md +7 -0
- package/package.json +13 -0
- package/src/index.ts +237 -0
package/CHANGELOG.md
ADDED
package/package.json
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"name": "@pioneer-platform/default-mongo-v2",
|
3
|
+
"version": "1.0.1",
|
4
|
+
"description": "Bun & ts-node compatible MongoDB client wrapper (no monk)",
|
5
|
+
"main": "src/index.ts",
|
6
|
+
"dependencies": {
|
7
|
+
"mongodb": "^6.10.0",
|
8
|
+
"dotenv": "^16.0.0"
|
9
|
+
},
|
10
|
+
"devDependencies": {
|
11
|
+
"@types/node": "^20.0.0"
|
12
|
+
}
|
13
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
/*
|
2
|
+
MongoDB Client V2 - Bun & ts-node Compatible
|
3
|
+
|
4
|
+
Modern MongoDB driver (v6) compatible with both Bun and Node.js runtimes.
|
5
|
+
Provides a simple interface similar to monk for backward compatibility.
|
6
|
+
No top-level await - lazy initialization on first use.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import { MongoClient, Db, Collection, Document, Filter, FindOptions, UpdateFilter } from 'mongodb';
|
10
|
+
|
11
|
+
const TAG = ' | mongo-v2 | ';
|
12
|
+
|
13
|
+
// MongoDB connection singleton
|
14
|
+
let client: MongoClient | null = null;
|
15
|
+
let defaultDb: Db | null = null;
|
16
|
+
let initializationPromise: Promise<void> | null = null;
|
17
|
+
let isInitialized = false;
|
18
|
+
|
19
|
+
// Get connection string from environment
|
20
|
+
const getConnectionString = (): string => {
|
21
|
+
let connectionString = process.env.MONGO_CONNECTION;
|
22
|
+
|
23
|
+
if (!connectionString) {
|
24
|
+
console.log(TAG, "Looking for mongo on: 127.0.0.1:27017/pioneer");
|
25
|
+
connectionString = "mongodb://127.0.0.1:27017/pioneer";
|
26
|
+
} else if (!connectionString.startsWith('mongodb://') && !connectionString.startsWith('mongodb+srv://')) {
|
27
|
+
// Convert old format to new format
|
28
|
+
if (connectionString.includes('@')) {
|
29
|
+
const [auth, hosts] = connectionString.split('@');
|
30
|
+
const [username, password] = auth.split(':');
|
31
|
+
|
32
|
+
if (hosts && hosts.includes('/')) {
|
33
|
+
const [hostList, dbAndParams] = hosts.split('/', 2);
|
34
|
+
const [dbName, ...params] = dbAndParams ? dbAndParams.split('?') : ['pioneer'];
|
35
|
+
const paramsStr = params.length ? `?${params.join('?')}` : '';
|
36
|
+
|
37
|
+
connectionString = `mongodb://${username}:${password}@${hostList}/${dbName}${paramsStr}`;
|
38
|
+
console.log(TAG, "Reformatted MongoDB connection string to new format");
|
39
|
+
}
|
40
|
+
} else {
|
41
|
+
// Simple format without auth
|
42
|
+
connectionString = `mongodb://${connectionString}`;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
return connectionString;
|
47
|
+
};
|
48
|
+
|
49
|
+
// Initialize MongoDB connection (async, called lazily)
|
50
|
+
const connect = async (): Promise<MongoClient> => {
|
51
|
+
if (client) {
|
52
|
+
return client;
|
53
|
+
}
|
54
|
+
|
55
|
+
try {
|
56
|
+
const connectionString = getConnectionString();
|
57
|
+
client = new MongoClient(connectionString);
|
58
|
+
|
59
|
+
await client.connect();
|
60
|
+
console.log(TAG, "Connected to MongoDB successfully");
|
61
|
+
|
62
|
+
// Extract database name from connection string
|
63
|
+
const url = new URL(connectionString);
|
64
|
+
const dbName = url.pathname.slice(1).split('?')[0] || 'pioneer';
|
65
|
+
defaultDb = client.db(dbName);
|
66
|
+
isInitialized = true;
|
67
|
+
|
68
|
+
return client;
|
69
|
+
} catch (error) {
|
70
|
+
console.error(TAG, "Failed to connect to MongoDB:", error);
|
71
|
+
console.error(TAG, "Server will continue but database operations may fail");
|
72
|
+
throw error;
|
73
|
+
}
|
74
|
+
};
|
75
|
+
|
76
|
+
// Collection wrapper with monk-like interface
|
77
|
+
class CollectionWrapper<T extends Document = Document> {
|
78
|
+
private collection: Collection<T>;
|
79
|
+
|
80
|
+
constructor(collection: Collection<T>) {
|
81
|
+
this.collection = collection;
|
82
|
+
}
|
83
|
+
|
84
|
+
// Find documents (returns array like monk)
|
85
|
+
async find(query: Filter<T> = {}, options: FindOptions = {}): Promise<T[]> {
|
86
|
+
return await this.collection.find(query, options).toArray();
|
87
|
+
}
|
88
|
+
|
89
|
+
// Find one document
|
90
|
+
async findOne(query: Filter<T>): Promise<T | null> {
|
91
|
+
return await this.collection.findOne(query);
|
92
|
+
}
|
93
|
+
|
94
|
+
// Insert one document
|
95
|
+
async insert(doc: Partial<T>): Promise<T> {
|
96
|
+
const result = await this.collection.insertOne(doc as any);
|
97
|
+
return { ...doc, _id: result.insertedId } as T;
|
98
|
+
}
|
99
|
+
|
100
|
+
// Update documents
|
101
|
+
async update(query: Filter<T>, update: UpdateFilter<T> | Partial<T>, options: any = {}): Promise<any> {
|
102
|
+
const updateDoc = ('$set' in update || '$inc' in update || '$push' in update)
|
103
|
+
? update as UpdateFilter<T>
|
104
|
+
: { $set: update };
|
105
|
+
|
106
|
+
if (options.multi) {
|
107
|
+
return await this.collection.updateMany(query, updateDoc);
|
108
|
+
} else {
|
109
|
+
return await this.collection.updateOne(query, updateDoc);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
// Remove documents
|
114
|
+
async remove(query: Filter<T>): Promise<any> {
|
115
|
+
return await this.collection.deleteMany(query);
|
116
|
+
}
|
117
|
+
|
118
|
+
// Count documents
|
119
|
+
async count(query: Filter<T> = {}): Promise<number> {
|
120
|
+
return await this.collection.countDocuments(query);
|
121
|
+
}
|
122
|
+
|
123
|
+
// Create index
|
124
|
+
async createIndex(keys: any, options: any = {}): Promise<string> {
|
125
|
+
return await this.collection.createIndex(keys, options);
|
126
|
+
}
|
127
|
+
|
128
|
+
// Drop collection
|
129
|
+
async drop(): Promise<boolean> {
|
130
|
+
return await this.collection.drop();
|
131
|
+
}
|
132
|
+
|
133
|
+
// Access native collection for advanced operations
|
134
|
+
async native(): Promise<Collection<T>> {
|
135
|
+
return this.collection;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
// Connection object with monk-like interface
|
140
|
+
class MongoConnection {
|
141
|
+
private client: MongoClient | null = null;
|
142
|
+
private defaultDb: Db | null = null;
|
143
|
+
private connections: Map<string, Db> = new Map();
|
144
|
+
private initPromise: Promise<void> | null = null;
|
145
|
+
|
146
|
+
// Initialize connection (idempotent)
|
147
|
+
async init(): Promise<void> {
|
148
|
+
if (this.initPromise) {
|
149
|
+
return this.initPromise;
|
150
|
+
}
|
151
|
+
|
152
|
+
this.initPromise = (async () => {
|
153
|
+
if (this.client) return; // Already initialized
|
154
|
+
|
155
|
+
this.client = await connect();
|
156
|
+
const connectionString = getConnectionString();
|
157
|
+
const url = new URL(connectionString);
|
158
|
+
const dbName = url.pathname.slice(1).split('?')[0] || 'pioneer';
|
159
|
+
this.defaultDb = this.client.db(dbName);
|
160
|
+
})();
|
161
|
+
|
162
|
+
return this.initPromise;
|
163
|
+
}
|
164
|
+
|
165
|
+
// Get collection from default database (monk-compatible - synchronous)
|
166
|
+
get<T extends Document = Document>(collectionName: string, dbName?: string): CollectionWrapper<T> {
|
167
|
+
// Trigger lazy initialization (non-blocking)
|
168
|
+
if (!isInitialized && !initializationPromise) {
|
169
|
+
initializationPromise = this.init().catch(err => {
|
170
|
+
console.error(TAG, "Lazy initialization failed:", err);
|
171
|
+
});
|
172
|
+
}
|
173
|
+
|
174
|
+
// Return wrapper immediately (will fail gracefully if not connected)
|
175
|
+
if (!defaultDb && !dbName) {
|
176
|
+
// Create a stub that will work once initialized
|
177
|
+
const self = this;
|
178
|
+
const stub = new Proxy({} as CollectionWrapper<T>, {
|
179
|
+
get(target, prop) {
|
180
|
+
if (!self.defaultDb && !dbName) {
|
181
|
+
console.warn(TAG, `MongoDB not yet initialized, operation '${String(prop)}' may fail`);
|
182
|
+
}
|
183
|
+
const db = dbName ? self.getDb(dbName) : self.defaultDb!;
|
184
|
+
const collection = db.collection<T>(collectionName);
|
185
|
+
const wrapper = new CollectionWrapper<T>(collection);
|
186
|
+
return (wrapper as any)[prop];
|
187
|
+
}
|
188
|
+
});
|
189
|
+
return stub;
|
190
|
+
}
|
191
|
+
|
192
|
+
const db = dbName ? this.getDb(dbName) : this.defaultDb!;
|
193
|
+
const collection = db.collection<T>(collectionName);
|
194
|
+
return new CollectionWrapper<T>(collection);
|
195
|
+
}
|
196
|
+
|
197
|
+
// Get database by name
|
198
|
+
getDb(dbName: string): Db {
|
199
|
+
if (!this.client) {
|
200
|
+
throw new Error("MongoDB client not initialized");
|
201
|
+
}
|
202
|
+
|
203
|
+
if (!this.connections.has(dbName)) {
|
204
|
+
this.connections.set(dbName, this.client.db(dbName));
|
205
|
+
}
|
206
|
+
|
207
|
+
return this.connections.get(dbName)!;
|
208
|
+
}
|
209
|
+
|
210
|
+
// Get collection from specific database (monk-like getFromDb)
|
211
|
+
getFromDb<T extends Document = Document>(collectionName: string, dbName: string): CollectionWrapper<T> {
|
212
|
+
return this.get<T>(collectionName, dbName);
|
213
|
+
}
|
214
|
+
|
215
|
+
// Close connection
|
216
|
+
async close(): Promise<void> {
|
217
|
+
if (this.client) {
|
218
|
+
await this.client.close();
|
219
|
+
this.client = null;
|
220
|
+
this.defaultDb = null;
|
221
|
+
this.connections.clear();
|
222
|
+
this.initPromise = null;
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
// Get native MongoDB client for advanced operations
|
227
|
+
get native(): MongoClient | null {
|
228
|
+
return this.client;
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
// Create and export connection instance (no top-level await)
|
233
|
+
const connection = new MongoConnection();
|
234
|
+
|
235
|
+
// ES module export (Bun handles both import and require automatically)
|
236
|
+
export default connection;
|
237
|
+
export { MongoConnection, CollectionWrapper };
|