@gravito/dark-matter 1.0.0-alpha.2
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 +182 -0
- package/dist/index.cjs +564 -0
- package/dist/index.mjs +538 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @gravito/dark-matter
|
|
2
|
+
|
|
3
|
+
> MongoDB client for Gravito - Bun native, Laravel-style API
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @gravito/dark-matter mongodb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Mongo } from '@gravito/dark-matter'
|
|
15
|
+
|
|
16
|
+
// Configure
|
|
17
|
+
Mongo.configure({
|
|
18
|
+
default: 'main',
|
|
19
|
+
connections: {
|
|
20
|
+
main: { uri: 'mongodb://localhost:27017', database: 'myapp' }
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Connect
|
|
25
|
+
await Mongo.connect()
|
|
26
|
+
|
|
27
|
+
// Use
|
|
28
|
+
const users = await Mongo.collection('users')
|
|
29
|
+
.where('status', 'active')
|
|
30
|
+
.orderBy('createdAt', 'desc')
|
|
31
|
+
.limit(10)
|
|
32
|
+
.get()
|
|
33
|
+
|
|
34
|
+
// Disconnect
|
|
35
|
+
await Mongo.disconnect()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- 🚀 **Bun Native** - Optimized for Bun runtime
|
|
41
|
+
- 🎯 **Laravel-style API** - Familiar fluent interface
|
|
42
|
+
- 🔍 **Query Builder** - Type-safe query building
|
|
43
|
+
- 📊 **Aggregation Pipeline** - Fluent aggregation API
|
|
44
|
+
- 🔌 **Multi-connection** - Named connections support
|
|
45
|
+
|
|
46
|
+
## API Reference
|
|
47
|
+
|
|
48
|
+
### Query Builder
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Basic queries
|
|
52
|
+
const users = await Mongo.collection('users')
|
|
53
|
+
.where('status', 'active')
|
|
54
|
+
.where('age', '>', 18)
|
|
55
|
+
.whereIn('role', ['admin', 'editor'])
|
|
56
|
+
.get()
|
|
57
|
+
|
|
58
|
+
// Sorting & Pagination
|
|
59
|
+
const latest = await Mongo.collection('posts')
|
|
60
|
+
.orderBy('createdAt', 'desc')
|
|
61
|
+
.limit(10)
|
|
62
|
+
.skip(20)
|
|
63
|
+
.get()
|
|
64
|
+
|
|
65
|
+
// Select specific fields
|
|
66
|
+
const names = await Mongo.collection('users')
|
|
67
|
+
.select('name', 'email')
|
|
68
|
+
.get()
|
|
69
|
+
|
|
70
|
+
// Find by ID
|
|
71
|
+
const user = await Mongo.collection('users').find('507f1f77bcf86cd799439011')
|
|
72
|
+
|
|
73
|
+
// Count & Exists
|
|
74
|
+
const count = await Mongo.collection('users').where('status', 'active').count()
|
|
75
|
+
const exists = await Mongo.collection('users').where('email', 'john@example.com').exists()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### CRUD Operations
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Insert
|
|
82
|
+
const result = await Mongo.collection('users').insert({
|
|
83
|
+
name: 'John',
|
|
84
|
+
email: 'john@example.com',
|
|
85
|
+
createdAt: new Date()
|
|
86
|
+
})
|
|
87
|
+
console.log(result.insertedId)
|
|
88
|
+
|
|
89
|
+
// Insert Many
|
|
90
|
+
const results = await Mongo.collection('users').insertMany([
|
|
91
|
+
{ name: 'Alice' },
|
|
92
|
+
{ name: 'Bob' }
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
// Update
|
|
96
|
+
await Mongo.collection('users')
|
|
97
|
+
.where('_id', userId)
|
|
98
|
+
.update({ status: 'inactive' })
|
|
99
|
+
|
|
100
|
+
// Update Many
|
|
101
|
+
await Mongo.collection('users')
|
|
102
|
+
.where('status', 'pending')
|
|
103
|
+
.updateMany({ status: 'active' })
|
|
104
|
+
|
|
105
|
+
// Delete
|
|
106
|
+
await Mongo.collection('users')
|
|
107
|
+
.where('_id', userId)
|
|
108
|
+
.delete()
|
|
109
|
+
|
|
110
|
+
// Delete Many
|
|
111
|
+
await Mongo.collection('users')
|
|
112
|
+
.where('status', 'deleted')
|
|
113
|
+
.deleteMany()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Aggregation Pipeline
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// Group and count
|
|
120
|
+
const stats = await Mongo.collection('orders')
|
|
121
|
+
.aggregate()
|
|
122
|
+
.match({ status: 'completed' })
|
|
123
|
+
.group({
|
|
124
|
+
_id: '$customerId',
|
|
125
|
+
totalOrders: { $sum: 1 },
|
|
126
|
+
totalAmount: { $sum: '$amount' }
|
|
127
|
+
})
|
|
128
|
+
.sort({ totalAmount: 'desc' })
|
|
129
|
+
.limit(10)
|
|
130
|
+
.get()
|
|
131
|
+
|
|
132
|
+
// Lookup (JOIN)
|
|
133
|
+
const ordersWithCustomers = await Mongo.collection('orders')
|
|
134
|
+
.aggregate()
|
|
135
|
+
.lookup({
|
|
136
|
+
from: 'customers',
|
|
137
|
+
localField: 'customerId',
|
|
138
|
+
foreignField: '_id',
|
|
139
|
+
as: 'customer'
|
|
140
|
+
})
|
|
141
|
+
.unwind('$customer')
|
|
142
|
+
.get()
|
|
143
|
+
|
|
144
|
+
// Project specific fields
|
|
145
|
+
const projected = await Mongo.collection('users')
|
|
146
|
+
.aggregate()
|
|
147
|
+
.project({
|
|
148
|
+
name: 1,
|
|
149
|
+
email: 1,
|
|
150
|
+
fullName: { $concat: ['$firstName', ' ', '$lastName'] }
|
|
151
|
+
})
|
|
152
|
+
.get()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Multiple Connections
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
Mongo.configure({
|
|
159
|
+
default: 'main',
|
|
160
|
+
connections: {
|
|
161
|
+
main: { uri: 'mongodb://localhost:27017', database: 'app' },
|
|
162
|
+
analytics: { uri: 'mongodb://analytics.example.com:27017', database: 'analytics' }
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Use specific connection
|
|
167
|
+
const data = await Mongo.connection('analytics')
|
|
168
|
+
.collection('events')
|
|
169
|
+
.where('type', 'pageview')
|
|
170
|
+
.get()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Roadmap
|
|
174
|
+
|
|
175
|
+
- [ ] Schema validation
|
|
176
|
+
- [ ] Transactions
|
|
177
|
+
- [ ] Change streams
|
|
178
|
+
- [ ] GridFS support
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
19
|
+
var __toCommonJS = (from) => {
|
|
20
|
+
var entry = __moduleCache.get(from), desc;
|
|
21
|
+
if (entry)
|
|
22
|
+
return entry;
|
|
23
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
25
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
26
|
+
get: () => from[key],
|
|
27
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
28
|
+
}));
|
|
29
|
+
__moduleCache.set(from, entry);
|
|
30
|
+
return entry;
|
|
31
|
+
};
|
|
32
|
+
var __export = (target, all) => {
|
|
33
|
+
for (var name in all)
|
|
34
|
+
__defProp(target, name, {
|
|
35
|
+
get: all[name],
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
set: (newValue) => all[name] = () => newValue
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/index.ts
|
|
43
|
+
var exports_src = {};
|
|
44
|
+
__export(exports_src, {
|
|
45
|
+
MongoQueryBuilder: () => MongoQueryBuilder,
|
|
46
|
+
MongoManager: () => MongoManager,
|
|
47
|
+
MongoClient: () => MongoClient,
|
|
48
|
+
MongoAggregateBuilder: () => MongoAggregateBuilder,
|
|
49
|
+
Mongo: () => Mongo
|
|
50
|
+
});
|
|
51
|
+
module.exports = __toCommonJS(exports_src);
|
|
52
|
+
|
|
53
|
+
// src/MongoQueryBuilder.ts
|
|
54
|
+
class MongoQueryBuilder {
|
|
55
|
+
nativeCollection;
|
|
56
|
+
collectionName;
|
|
57
|
+
filters = {};
|
|
58
|
+
orFilters = [];
|
|
59
|
+
projection = {};
|
|
60
|
+
sortSpec = {};
|
|
61
|
+
limitCount;
|
|
62
|
+
skipCount;
|
|
63
|
+
constructor(nativeCollection, collectionName) {
|
|
64
|
+
this.nativeCollection = nativeCollection;
|
|
65
|
+
this.collectionName = collectionName;
|
|
66
|
+
}
|
|
67
|
+
where(field, operatorOrValue, value) {
|
|
68
|
+
if (value === undefined) {
|
|
69
|
+
this.filters[field] = operatorOrValue;
|
|
70
|
+
} else {
|
|
71
|
+
const operator = operatorOrValue;
|
|
72
|
+
this.filters[field] = this.mapOperator(operator, value);
|
|
73
|
+
}
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
orWhere(field, operatorOrValue, value) {
|
|
77
|
+
const filter = {};
|
|
78
|
+
if (value === undefined) {
|
|
79
|
+
filter[field] = operatorOrValue;
|
|
80
|
+
} else {
|
|
81
|
+
const operator = operatorOrValue;
|
|
82
|
+
filter[field] = this.mapOperator(operator, value);
|
|
83
|
+
}
|
|
84
|
+
this.orFilters.push(filter);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
whereIn(field, values) {
|
|
88
|
+
this.filters[field] = { $in: values };
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
whereNotIn(field, values) {
|
|
92
|
+
this.filters[field] = { $nin: values };
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
whereNull(field) {
|
|
96
|
+
this.filters[field] = null;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
whereNotNull(field) {
|
|
100
|
+
this.filters[field] = { $ne: null };
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
whereExists(field, exists = true) {
|
|
104
|
+
this.filters[field] = { $exists: exists };
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
whereRegex(field, pattern) {
|
|
108
|
+
this.filters[field] = { $regex: pattern };
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
select(...fields) {
|
|
112
|
+
for (const field of fields) {
|
|
113
|
+
this.projection[field] = 1;
|
|
114
|
+
}
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
exclude(...fields) {
|
|
118
|
+
for (const field of fields) {
|
|
119
|
+
this.projection[field] = 0;
|
|
120
|
+
}
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
orderBy(field, direction = "asc") {
|
|
124
|
+
this.sortSpec[field] = direction === "asc" || direction === 1 ? 1 : -1;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
latest(field = "createdAt") {
|
|
128
|
+
return this.orderBy(field, "desc");
|
|
129
|
+
}
|
|
130
|
+
oldest(field = "createdAt") {
|
|
131
|
+
return this.orderBy(field, "asc");
|
|
132
|
+
}
|
|
133
|
+
limit(count) {
|
|
134
|
+
this.limitCount = count;
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
skip(count) {
|
|
138
|
+
this.skipCount = count;
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
offset(count) {
|
|
142
|
+
return this.skip(count);
|
|
143
|
+
}
|
|
144
|
+
async get() {
|
|
145
|
+
const cursor = this.nativeCollection.find(this.toFilter(), {
|
|
146
|
+
projection: Object.keys(this.projection).length > 0 ? this.projection : undefined,
|
|
147
|
+
sort: Object.keys(this.sortSpec).length > 0 ? this.sortSpec : undefined,
|
|
148
|
+
limit: this.limitCount,
|
|
149
|
+
skip: this.skipCount
|
|
150
|
+
});
|
|
151
|
+
return await cursor.toArray();
|
|
152
|
+
}
|
|
153
|
+
async first() {
|
|
154
|
+
const result = await this.nativeCollection.findOne(this.toFilter(), {
|
|
155
|
+
projection: Object.keys(this.projection).length > 0 ? this.projection : undefined,
|
|
156
|
+
sort: Object.keys(this.sortSpec).length > 0 ? this.sortSpec : undefined
|
|
157
|
+
});
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
async find(id) {
|
|
161
|
+
const { ObjectId } = await this.loadObjectId();
|
|
162
|
+
const result = await this.nativeCollection.findOne({ _id: new ObjectId(id) }, { projection: Object.keys(this.projection).length > 0 ? this.projection : undefined });
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
async count() {
|
|
166
|
+
return await this.nativeCollection.countDocuments(this.toFilter());
|
|
167
|
+
}
|
|
168
|
+
async exists() {
|
|
169
|
+
const count = await this.nativeCollection.countDocuments(this.toFilter(), { limit: 1 });
|
|
170
|
+
return count > 0;
|
|
171
|
+
}
|
|
172
|
+
async distinct(field) {
|
|
173
|
+
return await this.nativeCollection.distinct(field, this.toFilter());
|
|
174
|
+
}
|
|
175
|
+
async insert(document) {
|
|
176
|
+
const result = await this.nativeCollection.insertOne(document);
|
|
177
|
+
return {
|
|
178
|
+
insertedId: result.insertedId.toString(),
|
|
179
|
+
acknowledged: result.acknowledged
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async insertMany(documents) {
|
|
183
|
+
const result = await this.nativeCollection.insertMany(documents);
|
|
184
|
+
return {
|
|
185
|
+
insertedIds: Object.values(result.insertedIds).map((id) => id.toString()),
|
|
186
|
+
insertedCount: result.insertedCount,
|
|
187
|
+
acknowledged: result.acknowledged
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async update(update) {
|
|
191
|
+
const updateDoc = this.normalizeUpdate(update);
|
|
192
|
+
const result = await this.nativeCollection.updateOne(this.toFilter(), updateDoc);
|
|
193
|
+
return {
|
|
194
|
+
matchedCount: result.matchedCount,
|
|
195
|
+
modifiedCount: result.modifiedCount,
|
|
196
|
+
acknowledged: result.acknowledged,
|
|
197
|
+
...result.upsertedId ? { upsertedId: result.upsertedId.toString() } : {}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async updateMany(update) {
|
|
201
|
+
const updateDoc = this.normalizeUpdate(update);
|
|
202
|
+
const result = await this.nativeCollection.updateMany(this.toFilter(), updateDoc);
|
|
203
|
+
return {
|
|
204
|
+
matchedCount: result.matchedCount,
|
|
205
|
+
modifiedCount: result.modifiedCount,
|
|
206
|
+
acknowledged: result.acknowledged,
|
|
207
|
+
...result.upsertedId ? { upsertedId: result.upsertedId.toString() } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async delete() {
|
|
211
|
+
const result = await this.nativeCollection.deleteOne(this.toFilter());
|
|
212
|
+
return {
|
|
213
|
+
deletedCount: result.deletedCount,
|
|
214
|
+
acknowledged: result.acknowledged
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async deleteMany() {
|
|
218
|
+
const result = await this.nativeCollection.deleteMany(this.toFilter());
|
|
219
|
+
return {
|
|
220
|
+
deletedCount: result.deletedCount,
|
|
221
|
+
acknowledged: result.acknowledged
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
aggregate() {
|
|
225
|
+
return new MongoAggregateBuilder(this.nativeCollection, this.toFilter());
|
|
226
|
+
}
|
|
227
|
+
toFilter() {
|
|
228
|
+
if (this.orFilters.length === 0) {
|
|
229
|
+
return { ...this.filters };
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
$or: [this.filters, ...this.orFilters]
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
clone() {
|
|
236
|
+
const cloned = new MongoQueryBuilder(this.nativeCollection, this.collectionName);
|
|
237
|
+
cloned.filters = { ...this.filters };
|
|
238
|
+
cloned.orFilters = [...this.orFilters];
|
|
239
|
+
cloned.projection = { ...this.projection };
|
|
240
|
+
cloned.sortSpec = { ...this.sortSpec };
|
|
241
|
+
cloned.limitCount = this.limitCount;
|
|
242
|
+
cloned.skipCount = this.skipCount;
|
|
243
|
+
return cloned;
|
|
244
|
+
}
|
|
245
|
+
mapOperator(operator, value) {
|
|
246
|
+
switch (operator) {
|
|
247
|
+
case "=":
|
|
248
|
+
return value;
|
|
249
|
+
case "!=":
|
|
250
|
+
return { $ne: value };
|
|
251
|
+
case ">":
|
|
252
|
+
return { $gt: value };
|
|
253
|
+
case ">=":
|
|
254
|
+
return { $gte: value };
|
|
255
|
+
case "<":
|
|
256
|
+
return { $lt: value };
|
|
257
|
+
case "<=":
|
|
258
|
+
return { $lte: value };
|
|
259
|
+
case "in":
|
|
260
|
+
return { $in: value };
|
|
261
|
+
case "nin":
|
|
262
|
+
return { $nin: value };
|
|
263
|
+
case "exists":
|
|
264
|
+
return { $exists: value };
|
|
265
|
+
case "regex":
|
|
266
|
+
return { $regex: value };
|
|
267
|
+
default:
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
normalizeUpdate(update) {
|
|
272
|
+
const hasOperator = Object.keys(update).some((key) => key.startsWith("$"));
|
|
273
|
+
if (hasOperator) {
|
|
274
|
+
return update;
|
|
275
|
+
}
|
|
276
|
+
return { $set: update };
|
|
277
|
+
}
|
|
278
|
+
async loadObjectId() {
|
|
279
|
+
const mongodb = await import("mongodb");
|
|
280
|
+
return mongodb;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
class MongoAggregateBuilder {
|
|
285
|
+
nativeCollection;
|
|
286
|
+
pipeline = [];
|
|
287
|
+
constructor(nativeCollection, initialFilter) {
|
|
288
|
+
this.nativeCollection = nativeCollection;
|
|
289
|
+
if (initialFilter && Object.keys(initialFilter).length > 0) {
|
|
290
|
+
this.pipeline.push({ $match: initialFilter });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
match(filter) {
|
|
294
|
+
this.pipeline.push({ $match: filter });
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
group(spec) {
|
|
298
|
+
this.pipeline.push({ $group: spec });
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
project(projection) {
|
|
302
|
+
this.pipeline.push({ $project: projection });
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
sort(spec) {
|
|
306
|
+
const normalizedSpec = {};
|
|
307
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
308
|
+
normalizedSpec[key] = value === "asc" || value === 1 ? 1 : -1;
|
|
309
|
+
}
|
|
310
|
+
this.pipeline.push({ $sort: normalizedSpec });
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
limit(count) {
|
|
314
|
+
this.pipeline.push({ $limit: count });
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
skip(count) {
|
|
318
|
+
this.pipeline.push({ $skip: count });
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
unwind(field) {
|
|
322
|
+
this.pipeline.push({ $unwind: field });
|
|
323
|
+
return this;
|
|
324
|
+
}
|
|
325
|
+
lookup(options) {
|
|
326
|
+
this.pipeline.push({ $lookup: options });
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
addFields(fields) {
|
|
330
|
+
this.pipeline.push({ $addFields: fields });
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
count(fieldName) {
|
|
334
|
+
this.pipeline.push({ $count: fieldName });
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
async get() {
|
|
338
|
+
const cursor = this.nativeCollection.aggregate(this.pipeline);
|
|
339
|
+
return await cursor.toArray();
|
|
340
|
+
}
|
|
341
|
+
async first() {
|
|
342
|
+
this.pipeline.push({ $limit: 1 });
|
|
343
|
+
const results = await this.get();
|
|
344
|
+
return results[0] ?? null;
|
|
345
|
+
}
|
|
346
|
+
toPipeline() {
|
|
347
|
+
return [...this.pipeline];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/MongoClient.ts
|
|
352
|
+
class MongoClient {
|
|
353
|
+
config;
|
|
354
|
+
client = null;
|
|
355
|
+
db = null;
|
|
356
|
+
connected = false;
|
|
357
|
+
mongodb = null;
|
|
358
|
+
constructor(config = {}) {
|
|
359
|
+
this.config = config;
|
|
360
|
+
}
|
|
361
|
+
async connect() {
|
|
362
|
+
if (this.connected) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.mongodb = await this.loadMongoDBModule();
|
|
366
|
+
const uri = this.buildConnectionUri();
|
|
367
|
+
const options = {
|
|
368
|
+
maxPoolSize: this.config.maxPoolSize ?? 10,
|
|
369
|
+
minPoolSize: this.config.minPoolSize ?? 1
|
|
370
|
+
};
|
|
371
|
+
if (this.config.connectTimeoutMS) {
|
|
372
|
+
options.connectTimeoutMS = this.config.connectTimeoutMS;
|
|
373
|
+
}
|
|
374
|
+
if (this.config.socketTimeoutMS) {
|
|
375
|
+
options.socketTimeoutMS = this.config.socketTimeoutMS;
|
|
376
|
+
}
|
|
377
|
+
this.client = new this.mongodb.MongoClient(uri, options);
|
|
378
|
+
await this.client.connect();
|
|
379
|
+
const dbName = this.config.database ?? "test";
|
|
380
|
+
this.db = this.client.db(dbName);
|
|
381
|
+
this.connected = true;
|
|
382
|
+
}
|
|
383
|
+
async disconnect() {
|
|
384
|
+
if (this.client) {
|
|
385
|
+
await this.client.close();
|
|
386
|
+
this.client = null;
|
|
387
|
+
this.db = null;
|
|
388
|
+
}
|
|
389
|
+
this.connected = false;
|
|
390
|
+
}
|
|
391
|
+
isConnected() {
|
|
392
|
+
return this.connected && this.client !== null;
|
|
393
|
+
}
|
|
394
|
+
database(name) {
|
|
395
|
+
const client = this.getClient();
|
|
396
|
+
const db = name ? client.db(name) : this.db;
|
|
397
|
+
return new MongoDatabaseWrapper(db);
|
|
398
|
+
}
|
|
399
|
+
collection(name) {
|
|
400
|
+
const db = this.getDatabase();
|
|
401
|
+
const nativeCollection = db.collection(name);
|
|
402
|
+
return new MongoQueryBuilder(nativeCollection, name);
|
|
403
|
+
}
|
|
404
|
+
buildConnectionUri() {
|
|
405
|
+
if (this.config.uri) {
|
|
406
|
+
return this.config.uri;
|
|
407
|
+
}
|
|
408
|
+
const host = this.config.host ?? "localhost";
|
|
409
|
+
const port = this.config.port ?? 27017;
|
|
410
|
+
const protocol = this.config.tls ? "mongodb+srv" : "mongodb";
|
|
411
|
+
let uri = `${protocol}://`;
|
|
412
|
+
if (this.config.username && this.config.password) {
|
|
413
|
+
uri += `${encodeURIComponent(this.config.username)}:${encodeURIComponent(this.config.password)}@`;
|
|
414
|
+
}
|
|
415
|
+
uri += `${host}`;
|
|
416
|
+
if (!this.config.tls) {
|
|
417
|
+
uri += `:${port}`;
|
|
418
|
+
}
|
|
419
|
+
if (this.config.database) {
|
|
420
|
+
uri += `/${this.config.database}`;
|
|
421
|
+
}
|
|
422
|
+
const params = [];
|
|
423
|
+
if (this.config.authSource) {
|
|
424
|
+
params.push(`authSource=${this.config.authSource}`);
|
|
425
|
+
}
|
|
426
|
+
if (this.config.replicaSet) {
|
|
427
|
+
params.push(`replicaSet=${this.config.replicaSet}`);
|
|
428
|
+
}
|
|
429
|
+
if (params.length > 0) {
|
|
430
|
+
uri += `?${params.join("&")}`;
|
|
431
|
+
}
|
|
432
|
+
return uri;
|
|
433
|
+
}
|
|
434
|
+
async loadMongoDBModule() {
|
|
435
|
+
try {
|
|
436
|
+
const mongodb = await import("mongodb");
|
|
437
|
+
return mongodb;
|
|
438
|
+
} catch {
|
|
439
|
+
throw new Error('MongoDB client requires the "mongodb" package. Please install it: bun add mongodb');
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
getClient() {
|
|
443
|
+
if (!this.client) {
|
|
444
|
+
throw new Error("MongoDB client not connected. Call connect() first.");
|
|
445
|
+
}
|
|
446
|
+
return this.client;
|
|
447
|
+
}
|
|
448
|
+
getDatabase() {
|
|
449
|
+
if (!this.db) {
|
|
450
|
+
throw new Error("MongoDB client not connected. Call connect() first.");
|
|
451
|
+
}
|
|
452
|
+
return this.db;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class MongoDatabaseWrapper {
|
|
457
|
+
db;
|
|
458
|
+
constructor(db) {
|
|
459
|
+
this.db = db;
|
|
460
|
+
}
|
|
461
|
+
collection(name) {
|
|
462
|
+
const nativeCollection = this.db.collection(name);
|
|
463
|
+
return new MongoQueryBuilder(nativeCollection, name);
|
|
464
|
+
}
|
|
465
|
+
async listCollections() {
|
|
466
|
+
const collections = await this.db.listCollections().toArray();
|
|
467
|
+
return collections.map((c) => c.name);
|
|
468
|
+
}
|
|
469
|
+
async dropCollection(name) {
|
|
470
|
+
return await this.db.dropCollection(name);
|
|
471
|
+
}
|
|
472
|
+
async createCollection(name) {
|
|
473
|
+
await this.db.createCollection(name);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/MongoManager.ts
|
|
478
|
+
class MongoManager {
|
|
479
|
+
connections = new Map;
|
|
480
|
+
defaultConnection = "default";
|
|
481
|
+
configs = new Map;
|
|
482
|
+
configure(config) {
|
|
483
|
+
this.defaultConnection = config.default ?? "default";
|
|
484
|
+
for (const [name, connectionConfig] of Object.entries(config.connections)) {
|
|
485
|
+
this.configs.set(name, connectionConfig);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
addConnection(name, config) {
|
|
489
|
+
this.configs.set(name, config);
|
|
490
|
+
}
|
|
491
|
+
connection(name) {
|
|
492
|
+
const connectionName = name ?? this.defaultConnection;
|
|
493
|
+
if (!this.connections.has(connectionName)) {
|
|
494
|
+
const config = this.configs.get(connectionName);
|
|
495
|
+
if (!config) {
|
|
496
|
+
throw new Error(`MongoDB connection "${connectionName}" not configured`);
|
|
497
|
+
}
|
|
498
|
+
this.connections.set(connectionName, new MongoClient(config));
|
|
499
|
+
}
|
|
500
|
+
return this.connections.get(connectionName);
|
|
501
|
+
}
|
|
502
|
+
getDefault() {
|
|
503
|
+
return this.connection(this.defaultConnection);
|
|
504
|
+
}
|
|
505
|
+
async connectAll() {
|
|
506
|
+
for (const [name] of this.configs) {
|
|
507
|
+
const client = this.connection(name);
|
|
508
|
+
await client.connect();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async disconnectAll() {
|
|
512
|
+
for (const client of this.connections.values()) {
|
|
513
|
+
await client.disconnect();
|
|
514
|
+
}
|
|
515
|
+
this.connections.clear();
|
|
516
|
+
}
|
|
517
|
+
hasConnection(name) {
|
|
518
|
+
return this.configs.has(name);
|
|
519
|
+
}
|
|
520
|
+
async removeConnection(name) {
|
|
521
|
+
const client = this.connections.get(name);
|
|
522
|
+
if (client) {
|
|
523
|
+
await client.disconnect();
|
|
524
|
+
this.connections.delete(name);
|
|
525
|
+
}
|
|
526
|
+
this.configs.delete(name);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/Mongo.ts
|
|
531
|
+
var manager = new MongoManager;
|
|
532
|
+
|
|
533
|
+
class Mongo {
|
|
534
|
+
static configure(config) {
|
|
535
|
+
manager.configure(config);
|
|
536
|
+
}
|
|
537
|
+
static addConnection(name, config) {
|
|
538
|
+
manager.addConnection(name, config);
|
|
539
|
+
}
|
|
540
|
+
static connection(name) {
|
|
541
|
+
return manager.connection(name);
|
|
542
|
+
}
|
|
543
|
+
static async connect() {
|
|
544
|
+
await manager.getDefault().connect();
|
|
545
|
+
}
|
|
546
|
+
static async connectAll() {
|
|
547
|
+
await manager.connectAll();
|
|
548
|
+
}
|
|
549
|
+
static async disconnect() {
|
|
550
|
+
await manager.getDefault().disconnect();
|
|
551
|
+
}
|
|
552
|
+
static async disconnectAll() {
|
|
553
|
+
await manager.disconnectAll();
|
|
554
|
+
}
|
|
555
|
+
static isConnected() {
|
|
556
|
+
return manager.getDefault().isConnected();
|
|
557
|
+
}
|
|
558
|
+
static database(name) {
|
|
559
|
+
return manager.getDefault().database(name);
|
|
560
|
+
}
|
|
561
|
+
static collection(name) {
|
|
562
|
+
return manager.getDefault().collection(name);
|
|
563
|
+
}
|
|
564
|
+
}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/MongoQueryBuilder.ts
|
|
21
|
+
class MongoQueryBuilder {
|
|
22
|
+
nativeCollection;
|
|
23
|
+
collectionName;
|
|
24
|
+
filters = {};
|
|
25
|
+
orFilters = [];
|
|
26
|
+
projection = {};
|
|
27
|
+
sortSpec = {};
|
|
28
|
+
limitCount;
|
|
29
|
+
skipCount;
|
|
30
|
+
constructor(nativeCollection, collectionName) {
|
|
31
|
+
this.nativeCollection = nativeCollection;
|
|
32
|
+
this.collectionName = collectionName;
|
|
33
|
+
}
|
|
34
|
+
where(field, operatorOrValue, value) {
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
this.filters[field] = operatorOrValue;
|
|
37
|
+
} else {
|
|
38
|
+
const operator = operatorOrValue;
|
|
39
|
+
this.filters[field] = this.mapOperator(operator, value);
|
|
40
|
+
}
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
orWhere(field, operatorOrValue, value) {
|
|
44
|
+
const filter = {};
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
filter[field] = operatorOrValue;
|
|
47
|
+
} else {
|
|
48
|
+
const operator = operatorOrValue;
|
|
49
|
+
filter[field] = this.mapOperator(operator, value);
|
|
50
|
+
}
|
|
51
|
+
this.orFilters.push(filter);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
whereIn(field, values) {
|
|
55
|
+
this.filters[field] = { $in: values };
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
whereNotIn(field, values) {
|
|
59
|
+
this.filters[field] = { $nin: values };
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
whereNull(field) {
|
|
63
|
+
this.filters[field] = null;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
whereNotNull(field) {
|
|
67
|
+
this.filters[field] = { $ne: null };
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
whereExists(field, exists = true) {
|
|
71
|
+
this.filters[field] = { $exists: exists };
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
whereRegex(field, pattern) {
|
|
75
|
+
this.filters[field] = { $regex: pattern };
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
select(...fields) {
|
|
79
|
+
for (const field of fields) {
|
|
80
|
+
this.projection[field] = 1;
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
exclude(...fields) {
|
|
85
|
+
for (const field of fields) {
|
|
86
|
+
this.projection[field] = 0;
|
|
87
|
+
}
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
orderBy(field, direction = "asc") {
|
|
91
|
+
this.sortSpec[field] = direction === "asc" || direction === 1 ? 1 : -1;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
latest(field = "createdAt") {
|
|
95
|
+
return this.orderBy(field, "desc");
|
|
96
|
+
}
|
|
97
|
+
oldest(field = "createdAt") {
|
|
98
|
+
return this.orderBy(field, "asc");
|
|
99
|
+
}
|
|
100
|
+
limit(count) {
|
|
101
|
+
this.limitCount = count;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
skip(count) {
|
|
105
|
+
this.skipCount = count;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
offset(count) {
|
|
109
|
+
return this.skip(count);
|
|
110
|
+
}
|
|
111
|
+
async get() {
|
|
112
|
+
const cursor = this.nativeCollection.find(this.toFilter(), {
|
|
113
|
+
projection: Object.keys(this.projection).length > 0 ? this.projection : undefined,
|
|
114
|
+
sort: Object.keys(this.sortSpec).length > 0 ? this.sortSpec : undefined,
|
|
115
|
+
limit: this.limitCount,
|
|
116
|
+
skip: this.skipCount
|
|
117
|
+
});
|
|
118
|
+
return await cursor.toArray();
|
|
119
|
+
}
|
|
120
|
+
async first() {
|
|
121
|
+
const result = await this.nativeCollection.findOne(this.toFilter(), {
|
|
122
|
+
projection: Object.keys(this.projection).length > 0 ? this.projection : undefined,
|
|
123
|
+
sort: Object.keys(this.sortSpec).length > 0 ? this.sortSpec : undefined
|
|
124
|
+
});
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
async find(id) {
|
|
128
|
+
const { ObjectId } = await this.loadObjectId();
|
|
129
|
+
const result = await this.nativeCollection.findOne({ _id: new ObjectId(id) }, { projection: Object.keys(this.projection).length > 0 ? this.projection : undefined });
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
async count() {
|
|
133
|
+
return await this.nativeCollection.countDocuments(this.toFilter());
|
|
134
|
+
}
|
|
135
|
+
async exists() {
|
|
136
|
+
const count = await this.nativeCollection.countDocuments(this.toFilter(), { limit: 1 });
|
|
137
|
+
return count > 0;
|
|
138
|
+
}
|
|
139
|
+
async distinct(field) {
|
|
140
|
+
return await this.nativeCollection.distinct(field, this.toFilter());
|
|
141
|
+
}
|
|
142
|
+
async insert(document) {
|
|
143
|
+
const result = await this.nativeCollection.insertOne(document);
|
|
144
|
+
return {
|
|
145
|
+
insertedId: result.insertedId.toString(),
|
|
146
|
+
acknowledged: result.acknowledged
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async insertMany(documents) {
|
|
150
|
+
const result = await this.nativeCollection.insertMany(documents);
|
|
151
|
+
return {
|
|
152
|
+
insertedIds: Object.values(result.insertedIds).map((id) => id.toString()),
|
|
153
|
+
insertedCount: result.insertedCount,
|
|
154
|
+
acknowledged: result.acknowledged
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async update(update) {
|
|
158
|
+
const updateDoc = this.normalizeUpdate(update);
|
|
159
|
+
const result = await this.nativeCollection.updateOne(this.toFilter(), updateDoc);
|
|
160
|
+
return {
|
|
161
|
+
matchedCount: result.matchedCount,
|
|
162
|
+
modifiedCount: result.modifiedCount,
|
|
163
|
+
acknowledged: result.acknowledged,
|
|
164
|
+
...result.upsertedId ? { upsertedId: result.upsertedId.toString() } : {}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async updateMany(update) {
|
|
168
|
+
const updateDoc = this.normalizeUpdate(update);
|
|
169
|
+
const result = await this.nativeCollection.updateMany(this.toFilter(), updateDoc);
|
|
170
|
+
return {
|
|
171
|
+
matchedCount: result.matchedCount,
|
|
172
|
+
modifiedCount: result.modifiedCount,
|
|
173
|
+
acknowledged: result.acknowledged,
|
|
174
|
+
...result.upsertedId ? { upsertedId: result.upsertedId.toString() } : {}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async delete() {
|
|
178
|
+
const result = await this.nativeCollection.deleteOne(this.toFilter());
|
|
179
|
+
return {
|
|
180
|
+
deletedCount: result.deletedCount,
|
|
181
|
+
acknowledged: result.acknowledged
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async deleteMany() {
|
|
185
|
+
const result = await this.nativeCollection.deleteMany(this.toFilter());
|
|
186
|
+
return {
|
|
187
|
+
deletedCount: result.deletedCount,
|
|
188
|
+
acknowledged: result.acknowledged
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
aggregate() {
|
|
192
|
+
return new MongoAggregateBuilder(this.nativeCollection, this.toFilter());
|
|
193
|
+
}
|
|
194
|
+
toFilter() {
|
|
195
|
+
if (this.orFilters.length === 0) {
|
|
196
|
+
return { ...this.filters };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
$or: [this.filters, ...this.orFilters]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
clone() {
|
|
203
|
+
const cloned = new MongoQueryBuilder(this.nativeCollection, this.collectionName);
|
|
204
|
+
cloned.filters = { ...this.filters };
|
|
205
|
+
cloned.orFilters = [...this.orFilters];
|
|
206
|
+
cloned.projection = { ...this.projection };
|
|
207
|
+
cloned.sortSpec = { ...this.sortSpec };
|
|
208
|
+
cloned.limitCount = this.limitCount;
|
|
209
|
+
cloned.skipCount = this.skipCount;
|
|
210
|
+
return cloned;
|
|
211
|
+
}
|
|
212
|
+
mapOperator(operator, value) {
|
|
213
|
+
switch (operator) {
|
|
214
|
+
case "=":
|
|
215
|
+
return value;
|
|
216
|
+
case "!=":
|
|
217
|
+
return { $ne: value };
|
|
218
|
+
case ">":
|
|
219
|
+
return { $gt: value };
|
|
220
|
+
case ">=":
|
|
221
|
+
return { $gte: value };
|
|
222
|
+
case "<":
|
|
223
|
+
return { $lt: value };
|
|
224
|
+
case "<=":
|
|
225
|
+
return { $lte: value };
|
|
226
|
+
case "in":
|
|
227
|
+
return { $in: value };
|
|
228
|
+
case "nin":
|
|
229
|
+
return { $nin: value };
|
|
230
|
+
case "exists":
|
|
231
|
+
return { $exists: value };
|
|
232
|
+
case "regex":
|
|
233
|
+
return { $regex: value };
|
|
234
|
+
default:
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
normalizeUpdate(update) {
|
|
239
|
+
const hasOperator = Object.keys(update).some((key) => key.startsWith("$"));
|
|
240
|
+
if (hasOperator) {
|
|
241
|
+
return update;
|
|
242
|
+
}
|
|
243
|
+
return { $set: update };
|
|
244
|
+
}
|
|
245
|
+
async loadObjectId() {
|
|
246
|
+
const mongodb = await import("mongodb");
|
|
247
|
+
return mongodb;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
class MongoAggregateBuilder {
|
|
252
|
+
nativeCollection;
|
|
253
|
+
pipeline = [];
|
|
254
|
+
constructor(nativeCollection, initialFilter) {
|
|
255
|
+
this.nativeCollection = nativeCollection;
|
|
256
|
+
if (initialFilter && Object.keys(initialFilter).length > 0) {
|
|
257
|
+
this.pipeline.push({ $match: initialFilter });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
match(filter) {
|
|
261
|
+
this.pipeline.push({ $match: filter });
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
group(spec) {
|
|
265
|
+
this.pipeline.push({ $group: spec });
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
project(projection) {
|
|
269
|
+
this.pipeline.push({ $project: projection });
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
sort(spec) {
|
|
273
|
+
const normalizedSpec = {};
|
|
274
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
275
|
+
normalizedSpec[key] = value === "asc" || value === 1 ? 1 : -1;
|
|
276
|
+
}
|
|
277
|
+
this.pipeline.push({ $sort: normalizedSpec });
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
limit(count) {
|
|
281
|
+
this.pipeline.push({ $limit: count });
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
skip(count) {
|
|
285
|
+
this.pipeline.push({ $skip: count });
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
unwind(field) {
|
|
289
|
+
this.pipeline.push({ $unwind: field });
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
lookup(options) {
|
|
293
|
+
this.pipeline.push({ $lookup: options });
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
addFields(fields) {
|
|
297
|
+
this.pipeline.push({ $addFields: fields });
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
count(fieldName) {
|
|
301
|
+
this.pipeline.push({ $count: fieldName });
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
async get() {
|
|
305
|
+
const cursor = this.nativeCollection.aggregate(this.pipeline);
|
|
306
|
+
return await cursor.toArray();
|
|
307
|
+
}
|
|
308
|
+
async first() {
|
|
309
|
+
this.pipeline.push({ $limit: 1 });
|
|
310
|
+
const results = await this.get();
|
|
311
|
+
return results[0] ?? null;
|
|
312
|
+
}
|
|
313
|
+
toPipeline() {
|
|
314
|
+
return [...this.pipeline];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/MongoClient.ts
|
|
319
|
+
class MongoClient {
|
|
320
|
+
config;
|
|
321
|
+
client = null;
|
|
322
|
+
db = null;
|
|
323
|
+
connected = false;
|
|
324
|
+
mongodb = null;
|
|
325
|
+
constructor(config = {}) {
|
|
326
|
+
this.config = config;
|
|
327
|
+
}
|
|
328
|
+
async connect() {
|
|
329
|
+
if (this.connected) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this.mongodb = await this.loadMongoDBModule();
|
|
333
|
+
const uri = this.buildConnectionUri();
|
|
334
|
+
const options = {
|
|
335
|
+
maxPoolSize: this.config.maxPoolSize ?? 10,
|
|
336
|
+
minPoolSize: this.config.minPoolSize ?? 1
|
|
337
|
+
};
|
|
338
|
+
if (this.config.connectTimeoutMS) {
|
|
339
|
+
options.connectTimeoutMS = this.config.connectTimeoutMS;
|
|
340
|
+
}
|
|
341
|
+
if (this.config.socketTimeoutMS) {
|
|
342
|
+
options.socketTimeoutMS = this.config.socketTimeoutMS;
|
|
343
|
+
}
|
|
344
|
+
this.client = new this.mongodb.MongoClient(uri, options);
|
|
345
|
+
await this.client.connect();
|
|
346
|
+
const dbName = this.config.database ?? "test";
|
|
347
|
+
this.db = this.client.db(dbName);
|
|
348
|
+
this.connected = true;
|
|
349
|
+
}
|
|
350
|
+
async disconnect() {
|
|
351
|
+
if (this.client) {
|
|
352
|
+
await this.client.close();
|
|
353
|
+
this.client = null;
|
|
354
|
+
this.db = null;
|
|
355
|
+
}
|
|
356
|
+
this.connected = false;
|
|
357
|
+
}
|
|
358
|
+
isConnected() {
|
|
359
|
+
return this.connected && this.client !== null;
|
|
360
|
+
}
|
|
361
|
+
database(name) {
|
|
362
|
+
const client = this.getClient();
|
|
363
|
+
const db = name ? client.db(name) : this.db;
|
|
364
|
+
return new MongoDatabaseWrapper(db);
|
|
365
|
+
}
|
|
366
|
+
collection(name) {
|
|
367
|
+
const db = this.getDatabase();
|
|
368
|
+
const nativeCollection = db.collection(name);
|
|
369
|
+
return new MongoQueryBuilder(nativeCollection, name);
|
|
370
|
+
}
|
|
371
|
+
buildConnectionUri() {
|
|
372
|
+
if (this.config.uri) {
|
|
373
|
+
return this.config.uri;
|
|
374
|
+
}
|
|
375
|
+
const host = this.config.host ?? "localhost";
|
|
376
|
+
const port = this.config.port ?? 27017;
|
|
377
|
+
const protocol = this.config.tls ? "mongodb+srv" : "mongodb";
|
|
378
|
+
let uri = `${protocol}://`;
|
|
379
|
+
if (this.config.username && this.config.password) {
|
|
380
|
+
uri += `${encodeURIComponent(this.config.username)}:${encodeURIComponent(this.config.password)}@`;
|
|
381
|
+
}
|
|
382
|
+
uri += `${host}`;
|
|
383
|
+
if (!this.config.tls) {
|
|
384
|
+
uri += `:${port}`;
|
|
385
|
+
}
|
|
386
|
+
if (this.config.database) {
|
|
387
|
+
uri += `/${this.config.database}`;
|
|
388
|
+
}
|
|
389
|
+
const params = [];
|
|
390
|
+
if (this.config.authSource) {
|
|
391
|
+
params.push(`authSource=${this.config.authSource}`);
|
|
392
|
+
}
|
|
393
|
+
if (this.config.replicaSet) {
|
|
394
|
+
params.push(`replicaSet=${this.config.replicaSet}`);
|
|
395
|
+
}
|
|
396
|
+
if (params.length > 0) {
|
|
397
|
+
uri += `?${params.join("&")}`;
|
|
398
|
+
}
|
|
399
|
+
return uri;
|
|
400
|
+
}
|
|
401
|
+
async loadMongoDBModule() {
|
|
402
|
+
try {
|
|
403
|
+
const mongodb = await import("mongodb");
|
|
404
|
+
return mongodb;
|
|
405
|
+
} catch {
|
|
406
|
+
throw new Error('MongoDB client requires the "mongodb" package. Please install it: bun add mongodb');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
getClient() {
|
|
410
|
+
if (!this.client) {
|
|
411
|
+
throw new Error("MongoDB client not connected. Call connect() first.");
|
|
412
|
+
}
|
|
413
|
+
return this.client;
|
|
414
|
+
}
|
|
415
|
+
getDatabase() {
|
|
416
|
+
if (!this.db) {
|
|
417
|
+
throw new Error("MongoDB client not connected. Call connect() first.");
|
|
418
|
+
}
|
|
419
|
+
return this.db;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
class MongoDatabaseWrapper {
|
|
424
|
+
db;
|
|
425
|
+
constructor(db) {
|
|
426
|
+
this.db = db;
|
|
427
|
+
}
|
|
428
|
+
collection(name) {
|
|
429
|
+
const nativeCollection = this.db.collection(name);
|
|
430
|
+
return new MongoQueryBuilder(nativeCollection, name);
|
|
431
|
+
}
|
|
432
|
+
async listCollections() {
|
|
433
|
+
const collections = await this.db.listCollections().toArray();
|
|
434
|
+
return collections.map((c) => c.name);
|
|
435
|
+
}
|
|
436
|
+
async dropCollection(name) {
|
|
437
|
+
return await this.db.dropCollection(name);
|
|
438
|
+
}
|
|
439
|
+
async createCollection(name) {
|
|
440
|
+
await this.db.createCollection(name);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/MongoManager.ts
|
|
445
|
+
class MongoManager {
|
|
446
|
+
connections = new Map;
|
|
447
|
+
defaultConnection = "default";
|
|
448
|
+
configs = new Map;
|
|
449
|
+
configure(config) {
|
|
450
|
+
this.defaultConnection = config.default ?? "default";
|
|
451
|
+
for (const [name, connectionConfig] of Object.entries(config.connections)) {
|
|
452
|
+
this.configs.set(name, connectionConfig);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
addConnection(name, config) {
|
|
456
|
+
this.configs.set(name, config);
|
|
457
|
+
}
|
|
458
|
+
connection(name) {
|
|
459
|
+
const connectionName = name ?? this.defaultConnection;
|
|
460
|
+
if (!this.connections.has(connectionName)) {
|
|
461
|
+
const config = this.configs.get(connectionName);
|
|
462
|
+
if (!config) {
|
|
463
|
+
throw new Error(`MongoDB connection "${connectionName}" not configured`);
|
|
464
|
+
}
|
|
465
|
+
this.connections.set(connectionName, new MongoClient(config));
|
|
466
|
+
}
|
|
467
|
+
return this.connections.get(connectionName);
|
|
468
|
+
}
|
|
469
|
+
getDefault() {
|
|
470
|
+
return this.connection(this.defaultConnection);
|
|
471
|
+
}
|
|
472
|
+
async connectAll() {
|
|
473
|
+
for (const [name] of this.configs) {
|
|
474
|
+
const client = this.connection(name);
|
|
475
|
+
await client.connect();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async disconnectAll() {
|
|
479
|
+
for (const client of this.connections.values()) {
|
|
480
|
+
await client.disconnect();
|
|
481
|
+
}
|
|
482
|
+
this.connections.clear();
|
|
483
|
+
}
|
|
484
|
+
hasConnection(name) {
|
|
485
|
+
return this.configs.has(name);
|
|
486
|
+
}
|
|
487
|
+
async removeConnection(name) {
|
|
488
|
+
const client = this.connections.get(name);
|
|
489
|
+
if (client) {
|
|
490
|
+
await client.disconnect();
|
|
491
|
+
this.connections.delete(name);
|
|
492
|
+
}
|
|
493
|
+
this.configs.delete(name);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/Mongo.ts
|
|
498
|
+
var manager = new MongoManager;
|
|
499
|
+
|
|
500
|
+
class Mongo {
|
|
501
|
+
static configure(config) {
|
|
502
|
+
manager.configure(config);
|
|
503
|
+
}
|
|
504
|
+
static addConnection(name, config) {
|
|
505
|
+
manager.addConnection(name, config);
|
|
506
|
+
}
|
|
507
|
+
static connection(name) {
|
|
508
|
+
return manager.connection(name);
|
|
509
|
+
}
|
|
510
|
+
static async connect() {
|
|
511
|
+
await manager.getDefault().connect();
|
|
512
|
+
}
|
|
513
|
+
static async connectAll() {
|
|
514
|
+
await manager.connectAll();
|
|
515
|
+
}
|
|
516
|
+
static async disconnect() {
|
|
517
|
+
await manager.getDefault().disconnect();
|
|
518
|
+
}
|
|
519
|
+
static async disconnectAll() {
|
|
520
|
+
await manager.disconnectAll();
|
|
521
|
+
}
|
|
522
|
+
static isConnected() {
|
|
523
|
+
return manager.getDefault().isConnected();
|
|
524
|
+
}
|
|
525
|
+
static database(name) {
|
|
526
|
+
return manager.getDefault().database(name);
|
|
527
|
+
}
|
|
528
|
+
static collection(name) {
|
|
529
|
+
return manager.getDefault().collection(name);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
export {
|
|
533
|
+
MongoQueryBuilder,
|
|
534
|
+
MongoManager,
|
|
535
|
+
MongoClient,
|
|
536
|
+
MongoAggregateBuilder,
|
|
537
|
+
Mongo
|
|
538
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gravito/dark-matter",
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
|
+
"description": "MongoDB client for Gravito - Bun native, Laravel-style API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun run build.ts",
|
|
17
|
+
"test": "bun test",
|
|
18
|
+
"typecheck": "bunx tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"gravito",
|
|
26
|
+
"mongodb",
|
|
27
|
+
"nosql",
|
|
28
|
+
"bun",
|
|
29
|
+
"document-database"
|
|
30
|
+
],
|
|
31
|
+
"author": "Gravito Team",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"mongodb": "^6.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"mongodb": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|