babymongo 0.2.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/LICENSE +27 -0
- package/README.md +712 -0
- package/dist/babymongo-browser.min.js +2 -0
- package/dist/babymongo-browser.min.js.map +1 -0
- package/dist/babymongo-node.min.js +2 -0
- package/dist/babymongo-node.min.js.map +1 -0
- package/dist/babymongo-server-worker.js +2 -0
- package/dist/babymongo-server-worker.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
# BabyMongo
|
|
2
|
+
|
|
3
|
+
A JavaScript implementation of the MongoDB query API with persistent storage using the Origin Private File System (OPFS). BabyMongo runs database operations in a Web Worker (browser) or Worker Thread (Node.js).
|
|
4
|
+
|
|
5
|
+
**Key Features:**
|
|
6
|
+
- 🔄 **MongoDB-Compatible API:** Familiar MongoDB syntax for queries, updates, aggregations, and change streams
|
|
7
|
+
- 💾 **OPFS Persistent Storage:** Automatic persistence using the Origin Private File System (browser) with a Node.js polyfill for development
|
|
8
|
+
- 📦 **Zero Configuration:** Works out of the box in both browser and Node.js environments
|
|
9
|
+
- 🚀 **Web Worker Architecture:** Database operations run in a separate thread, keeping your UI responsive
|
|
10
|
+
- 🔍 **Advanced Features:** Indexes, text search, geospatial queries, aggregation pipelines, and real-time change streams
|
|
11
|
+
|
|
12
|
+
[](https://github.com/belteshazzar/babymongo/actions/workflows/test.yml) [](https://codecov.io/gh/belteshazzar/babymongo)
|
|
13
|
+
|
|
14
|
+
## In Node.js
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
`npm install babymongo`
|
|
19
|
+
|
|
20
|
+
### Usage
|
|
21
|
+
|
|
22
|
+
BabyMongo uses a web worker architecture to keep database operations off the main thread. The `WorkerBridge` manages communication between your application and the database worker:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
// Step 1: Create the worker bridge
|
|
29
|
+
const bridge = await WorkerBridge.create();
|
|
30
|
+
|
|
31
|
+
// Step 2: Connect to database with the bridge
|
|
32
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
33
|
+
workerBridge: bridge
|
|
34
|
+
});
|
|
35
|
+
await client.connect();
|
|
36
|
+
|
|
37
|
+
const db = client.db('myapp');
|
|
38
|
+
|
|
39
|
+
// Insert documents (async)
|
|
40
|
+
await db.sample.insertOne({ age: 4, legs: 0 });
|
|
41
|
+
await db.sample.insertMany([
|
|
42
|
+
{ age: 4, legs: 5 },
|
|
43
|
+
{ age: 54, legs: 2 }
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// Query documents with toArray() (async)
|
|
47
|
+
const results = await db.sample.find({ age: 54 }).toArray();
|
|
48
|
+
console.log(results);
|
|
49
|
+
|
|
50
|
+
// Or use async iteration
|
|
51
|
+
for await (const doc of db.sample.find({ legs: 2 })) {
|
|
52
|
+
console.log(doc);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update (async)
|
|
56
|
+
await db.sample.updateOne({ age: 4 }, { $set: { legs: 4 } });
|
|
57
|
+
|
|
58
|
+
// Delete (async)
|
|
59
|
+
await db.sample.deleteOne({ age: 54 });
|
|
60
|
+
|
|
61
|
+
// Count (async)
|
|
62
|
+
const count = await db.sample.count();
|
|
63
|
+
console.log(`Total documents: ${count}`);
|
|
64
|
+
|
|
65
|
+
// Close connection
|
|
66
|
+
await client.close();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch(console.error);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Why Web Workers?**
|
|
73
|
+
- ✅ Non-blocking operations keep your UI responsive
|
|
74
|
+
- ✅ Efficient isolation of database logic
|
|
75
|
+
- ✅ Better performance for large datasets
|
|
76
|
+
- ✅ Works seamlessly in both browser and Node.js
|
|
77
|
+
- ✅ Uses synchronous file access which is only available in Web Workers.
|
|
78
|
+
|
|
79
|
+
### Using Indexes
|
|
80
|
+
|
|
81
|
+
Indexes can significantly improve query performance for large collections:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
const bridge = await WorkerBridge.create();
|
|
88
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
89
|
+
workerBridge: bridge
|
|
90
|
+
});
|
|
91
|
+
await client.connect();
|
|
92
|
+
const db = client.db('myapp');
|
|
93
|
+
|
|
94
|
+
// Insert some data
|
|
95
|
+
await db.users.insertMany([
|
|
96
|
+
{ name: "Alice", age: 30, city: "NYC" },
|
|
97
|
+
{ name: "Bob", age: 25, city: "LA" },
|
|
98
|
+
{ name: "Charlie", age: 30, city: "SF" }
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// Create an index on the age field
|
|
102
|
+
await db.users.createIndex({ age: 1 });
|
|
103
|
+
|
|
104
|
+
// Create a named index on city
|
|
105
|
+
await db.users.createIndex({ city: 1 }, { name: "city_index" });
|
|
106
|
+
|
|
107
|
+
// Create a compound index
|
|
108
|
+
await db.users.createIndex({ age: 1, city: 1 });
|
|
109
|
+
|
|
110
|
+
// List all indexes
|
|
111
|
+
const indexes = await db.users.getIndexes();
|
|
112
|
+
|
|
113
|
+
// Queries will automatically use indexes when possible
|
|
114
|
+
const results = await db.users.find({ age: 30 }).toArray();
|
|
115
|
+
|
|
116
|
+
await client.close();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main().catch(console.error);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The query planner automatically uses indexes for simple equality queries. For complex queries, it combines index lookups with full collection scans to ensure complete and correct results.
|
|
123
|
+
|
|
124
|
+
### Persistent Storage with OPFS
|
|
125
|
+
|
|
126
|
+
BabyMongo uses the **Origin Private File System (OPFS)** for automatic data persistence. OPFS is a modern web standard that provides fast, private storage for web applications.
|
|
127
|
+
|
|
128
|
+
**In the Browser:**
|
|
129
|
+
|
|
130
|
+
Data is automatically persisted to the OPFS when you use the WorkerBridge pattern. No additional configuration needed!
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
const bridge = await WorkerBridge.create();
|
|
137
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
138
|
+
workerBridge: bridge
|
|
139
|
+
});
|
|
140
|
+
await client.connect();
|
|
141
|
+
const db = client.db('myapp');
|
|
142
|
+
|
|
143
|
+
// Data is automatically persisted to OPFS
|
|
144
|
+
await db.users.insertOne({ name: 'Alice', age: 30 });
|
|
145
|
+
await db.users.createIndex({ age: 1 });
|
|
146
|
+
|
|
147
|
+
// On page reload, your data and indexes are automatically restored!
|
|
148
|
+
|
|
149
|
+
await client.close();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main().catch(console.error);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**In Node.js:**
|
|
156
|
+
|
|
157
|
+
BabyMongo includes the [node-opfs](https://github.com/belteshazzar/node-opfs) polyfill, which implements the OPFS API using the file system. Data is stored in a `.opfs` directory in your project root.
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// Same code works in Node.js!
|
|
161
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
162
|
+
|
|
163
|
+
async function main() {
|
|
164
|
+
const bridge = await WorkerBridge.create();
|
|
165
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
166
|
+
workerBridge: bridge
|
|
167
|
+
});
|
|
168
|
+
await client.connect();
|
|
169
|
+
const db = client.db('myapp');
|
|
170
|
+
|
|
171
|
+
// Data is persisted to .opfs/babymongo/myapp/
|
|
172
|
+
await db.products.insertMany([
|
|
173
|
+
{ name: 'Widget', price: 10.99 },
|
|
174
|
+
{ name: 'Gadget', price: 24.99 }
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
await client.close();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
main().catch(console.error);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Storage Location:**
|
|
184
|
+
- **Browser:** Data stored in OPFS (private to your origin, not accessible via DevTools)
|
|
185
|
+
- **Node.js:** Data stored in `.opfs/babymongo/{database-name}/{collection-name}/` directory
|
|
186
|
+
- **Format:** Binary JSON (BJSON) with B+ tree indexing for efficient queries
|
|
187
|
+
|
|
188
|
+
**Benefits of OPFS:**
|
|
189
|
+
- ✅ **Fast:** Synchronous file access in workers for better performance
|
|
190
|
+
- ✅ **Private:** Data is isolated and secure
|
|
191
|
+
- ✅ **Automatic:** No manual save/load calls needed
|
|
192
|
+
- ✅ **Cross-platform:** Works identically in browser and Node.js (via polyfill)
|
|
193
|
+
- ✅ **Versioned:** Supports compaction and versioning to prevent data corruption
|
|
194
|
+
|
|
195
|
+
### Aggregation Pipelines
|
|
196
|
+
|
|
197
|
+
BabyMongo supports MongoDB's powerful aggregation framework for data transformation and analysis:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
201
|
+
|
|
202
|
+
async function main() {
|
|
203
|
+
const bridge = await WorkerBridge.create();
|
|
204
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
205
|
+
workerBridge: bridge
|
|
206
|
+
});
|
|
207
|
+
await client.connect();
|
|
208
|
+
const db = client.db('myapp');
|
|
209
|
+
|
|
210
|
+
// Sample data
|
|
211
|
+
await db.sales.insertMany([
|
|
212
|
+
{ product: 'Widget', price: 10, quantity: 5, category: 'Tools' },
|
|
213
|
+
{ product: 'Gadget', price: 20, quantity: 3, category: 'Electronics' },
|
|
214
|
+
{ product: 'Doohickey', price: 15, quantity: 2, category: 'Tools' }
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
// Aggregation pipeline
|
|
218
|
+
const results = await db.sales.aggregate([
|
|
219
|
+
{ $match: { category: 'Tools' } },
|
|
220
|
+
{ $addFields: { total: { $multiply: ['$price', '$quantity'] } } },
|
|
221
|
+
{ $group: {
|
|
222
|
+
_id: '$category',
|
|
223
|
+
totalSales: { $sum: '$total' },
|
|
224
|
+
avgPrice: { $avg: '$price' }
|
|
225
|
+
}},
|
|
226
|
+
{ $sort: { totalSales: -1 } }
|
|
227
|
+
]).toArray();
|
|
228
|
+
|
|
229
|
+
console.log(results);
|
|
230
|
+
// [{ _id: 'Tools', totalSales: 80, avgPrice: 12.5 }]
|
|
231
|
+
|
|
232
|
+
await client.close();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main().catch(console.error);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Supported Stages:**
|
|
239
|
+
- `$match` - Filter documents
|
|
240
|
+
- `$project` - Reshape documents with expressions
|
|
241
|
+
- `$addFields` / `$set` - Add computed fields
|
|
242
|
+
- `$unset` - Remove fields
|
|
243
|
+
- `$group` - Group and aggregate
|
|
244
|
+
- `$sort` - Sort results
|
|
245
|
+
- `$limit` / `$skip` - Pagination
|
|
246
|
+
- `$lookup` - Join collections
|
|
247
|
+
- `$graphLookup` - Recursive joins
|
|
248
|
+
- `$facet` - Multiple pipelines
|
|
249
|
+
- `$bucket` / `$bucketAuto` - Histogram grouping
|
|
250
|
+
- `$sortByCount` - Group and count
|
|
251
|
+
- `$geoNear` - Geospatial aggregation
|
|
252
|
+
- `$merge` - Write results to collection
|
|
253
|
+
|
|
254
|
+
**Aggregation Expressions:**
|
|
255
|
+
Supports arithmetic (`$add`, `$multiply`), comparison (`$eq`, `$gt`), logical (`$and`, `$or`), string (`$concat`, `$substr`), date (`$year`, `$month`), and array operators (`$size`, `$filter`, `$map`).
|
|
256
|
+
|
|
257
|
+
### Tests
|
|
258
|
+
|
|
259
|
+
`npm test`
|
|
260
|
+
|
|
261
|
+
### MongoDB Comparison Testing
|
|
262
|
+
|
|
263
|
+
BabyMongo includes a test harness for comparing behavior against real MongoDB to ensure API compatibility. This helps verify that babymongo behaves identically to MongoDB.
|
|
264
|
+
|
|
265
|
+
**Quick Start:**
|
|
266
|
+
|
|
267
|
+
1. Start MongoDB (using Docker):
|
|
268
|
+
```bash
|
|
269
|
+
docker run -d -p 27017:27017 --name mongodb mongo:latest
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
2. Install MongoDB driver:
|
|
273
|
+
```bash
|
|
274
|
+
npm install mongodb
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
3. Run comparison tests:
|
|
278
|
+
```bash
|
|
279
|
+
npm run test:comparison
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**What's tested:**
|
|
283
|
+
- ✅ CRUD operations (insert, find, update, delete)
|
|
284
|
+
- ✅ Query operators ($gt, $lt, $in, $and, $or, etc.)
|
|
285
|
+
- ✅ Update operators ($set, $inc, $push, $unset, etc.)
|
|
286
|
+
- ✅ Aggregation pipelines ($match, $group, $sort, $project, etc.)
|
|
287
|
+
- ✅ Complex queries (nested fields, arrays, $elemMatch, etc.)
|
|
288
|
+
|
|
289
|
+
For detailed documentation, see:
|
|
290
|
+
- [MongoDB Comparison Quick Start](docs/MONGODB_COMPARISON_QUICKSTART.md)
|
|
291
|
+
- [Complete Documentation](docs/MONGODB_COMPARISON.md)
|
|
292
|
+
|
|
293
|
+
## In the Browser
|
|
294
|
+
|
|
295
|
+
BabyMongo works seamlessly in modern browsers with automatic OPFS persistence.
|
|
296
|
+
|
|
297
|
+
### Installation
|
|
298
|
+
|
|
299
|
+
You can use BabyMongo from a CDN or build it yourself:
|
|
300
|
+
|
|
301
|
+
**Option 1: Use pre-built files**
|
|
302
|
+
|
|
303
|
+
Download the built files:
|
|
304
|
+
- Client: https://raw.githubusercontent.com/belteshazzar/babymongo/master/build/babymongo-client.js
|
|
305
|
+
- Worker: https://raw.githubusercontent.com/belteshazzar/babymongo/master/build/babymongo-server-worker.js
|
|
306
|
+
|
|
307
|
+
**Option 2: Build from source**
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npm install
|
|
311
|
+
npm run build
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Usage in Browser
|
|
315
|
+
|
|
316
|
+
```html
|
|
317
|
+
<!DOCTYPE html>
|
|
318
|
+
<html>
|
|
319
|
+
<head>
|
|
320
|
+
<title>BabyMongo Browser Example</title>
|
|
321
|
+
</head>
|
|
322
|
+
<body>
|
|
323
|
+
<h1>BabyMongo in Browser</h1>
|
|
324
|
+
<button id="insert">Insert Data</button>
|
|
325
|
+
<button id="query">Query Data</button>
|
|
326
|
+
<pre id="output"></pre>
|
|
327
|
+
|
|
328
|
+
<script type="module">
|
|
329
|
+
import { MongoClient, WorkerBridge } from './build/babymongo-client.js';
|
|
330
|
+
|
|
331
|
+
async function main() {
|
|
332
|
+
// Create worker bridge
|
|
333
|
+
const bridge = await WorkerBridge.create();
|
|
334
|
+
|
|
335
|
+
// Connect to database
|
|
336
|
+
const client = new MongoClient('mongodb://localhost:27017/browserdb', {
|
|
337
|
+
workerBridge: bridge
|
|
338
|
+
});
|
|
339
|
+
await client.connect();
|
|
340
|
+
const db = client.db('browserdb');
|
|
341
|
+
|
|
342
|
+
const output = document.getElementById('output');
|
|
343
|
+
|
|
344
|
+
// Insert button handler
|
|
345
|
+
document.getElementById('insert').addEventListener('click', async () => {
|
|
346
|
+
await db.items.insertOne({
|
|
347
|
+
name: 'Item ' + Date.now(),
|
|
348
|
+
timestamp: new Date()
|
|
349
|
+
});
|
|
350
|
+
output.textContent = 'Data inserted and persisted to OPFS!';
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Query button handler
|
|
354
|
+
document.getElementById('query').addEventListener('click', async () => {
|
|
355
|
+
const items = await db.items.find({}).toArray();
|
|
356
|
+
output.textContent = JSON.stringify(items, null, 2);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch(console.error);
|
|
361
|
+
</script>
|
|
362
|
+
</body>
|
|
363
|
+
</html>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Browser Features
|
|
367
|
+
|
|
368
|
+
- **Automatic Persistence:** All data is saved to OPFS and survives page reloads
|
|
369
|
+
- **Web Worker:** Database operations run in a worker thread for responsive UI
|
|
370
|
+
- **Full MongoDB API:** Query, update, aggregate, indexes, change streams
|
|
371
|
+
- **Developer-Friendly:** Check `.opfs` directory in Node.js for debugging
|
|
372
|
+
|
|
373
|
+
## Architecture
|
|
374
|
+
|
|
375
|
+
### Web Worker Design
|
|
376
|
+
|
|
377
|
+
BabyMongo uses a **dual-thread architecture** for optimal performance:
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
┌─────────────────────────────────┐
|
|
381
|
+
│ Main Thread (Your App) │
|
|
382
|
+
│ ┌───────────────────────────┐ │
|
|
383
|
+
│ │ MongoClient │ │
|
|
384
|
+
│ │ ProxyDB │ │
|
|
385
|
+
│ │ ProxyCollection │ │
|
|
386
|
+
│ └───────────┬───────────────┘ │
|
|
387
|
+
│ │ WorkerBridge │
|
|
388
|
+
└──────────────┼──────────────────┘
|
|
389
|
+
│ postMessage()
|
|
390
|
+
┌──────────────┼──────────────────┐
|
|
391
|
+
│ ▼ │
|
|
392
|
+
│ ┌───────────────────────────┐ │
|
|
393
|
+
│ │ Server │ │
|
|
394
|
+
│ │ DB │ │
|
|
395
|
+
│ │ Collection │ │
|
|
396
|
+
│ │ Indexes │ │
|
|
397
|
+
│ │ OPFS Storage │ │
|
|
398
|
+
│ └───────────────────────────┘ │
|
|
399
|
+
│ Worker Thread (Web Worker/ │
|
|
400
|
+
│ Worker Thread in Node.js) │
|
|
401
|
+
└─────────────────────────────────┘
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**How It Works:**
|
|
405
|
+
|
|
406
|
+
1. **Main Thread:** Your application code runs here. All MongoDB operations are proxied through `WorkerBridge`
|
|
407
|
+
2. **Worker Thread:** The actual database engine runs here with direct OPFS access
|
|
408
|
+
3. **Communication:** WorkerBridge serializes/deserializes messages between threads
|
|
409
|
+
4. **Benefits:**
|
|
410
|
+
- UI stays responsive during heavy database operations
|
|
411
|
+
- OPFS synchronous APIs available in worker context (fast!)
|
|
412
|
+
- Clean separation of concerns
|
|
413
|
+
|
|
414
|
+
### Storage with OPFS
|
|
415
|
+
|
|
416
|
+
The **Origin Private File System** provides fast, persistent storage:
|
|
417
|
+
|
|
418
|
+
**File Structure:**
|
|
419
|
+
```
|
|
420
|
+
.opfs/ # Root (Node.js) or OPFS root (Browser)
|
|
421
|
+
└── babymongo/ # Base folder
|
|
422
|
+
└── {database}/ # Database name
|
|
423
|
+
└── {collection}/ # Collection name
|
|
424
|
+
├── documents.bj # B+ tree of documents
|
|
425
|
+
├── documents.bj.version.json # Version metadata
|
|
426
|
+
├── documents.bj.v1 # Old version (during compaction)
|
|
427
|
+
└── {index-name}.bj # Index B+ trees
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Key Features:**
|
|
431
|
+
|
|
432
|
+
- **Binary JSON (BJSON):** Efficient binary format with ObjectId and Date support
|
|
433
|
+
- **B+ Tree Indexes:** Self-balancing trees for fast queries
|
|
434
|
+
- **Versioning:** Safe compaction without data loss
|
|
435
|
+
- **Reference Counting:** Old versions kept until all readers close
|
|
436
|
+
- **Automatic Cleanup:** Old versions deleted when no longer needed
|
|
437
|
+
|
|
438
|
+
**Example Storage:**
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
// After this code runs:
|
|
442
|
+
const db = client.db('store');
|
|
443
|
+
await db.products.insertOne({ name: 'Widget', price: 10.99 });
|
|
444
|
+
await db.products.createIndex({ name: 1 });
|
|
445
|
+
|
|
446
|
+
// Storage structure:
|
|
447
|
+
// .opfs/babymongo/store/products/documents.bj
|
|
448
|
+
// .opfs/babymongo/store/products/name_1.bj
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Multiple Clients Sharing a Worker
|
|
452
|
+
|
|
453
|
+
You can create multiple clients that share the same worker:
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
const bridge = await WorkerBridge.create();
|
|
457
|
+
|
|
458
|
+
const client1 = new MongoClient('mongodb://localhost/app1', { workerBridge: bridge });
|
|
459
|
+
const client2 = new MongoClient('mongodb://localhost/app2', { workerBridge: bridge });
|
|
460
|
+
|
|
461
|
+
await client1.connect();
|
|
462
|
+
await client2.connect();
|
|
463
|
+
|
|
464
|
+
// Both clients use the same worker thread
|
|
465
|
+
// Changes in one client are visible to the other
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
# API Status
|
|
469
|
+
|
|
470
|
+
The following table summarises the API implementation status.
|
|
471
|
+
|
|
472
|
+
## Database Methods
|
|
473
|
+
|
|
474
|
+
| Name | Implemented |
|
|
475
|
+
|------------------------------|-----------------|
|
|
476
|
+
| db.cloneCollection | no |
|
|
477
|
+
| db.cloneDatabase | no |
|
|
478
|
+
| db.commandHelp | no |
|
|
479
|
+
| db.copyDatabase | no |
|
|
480
|
+
| db.createCollection | Yes |
|
|
481
|
+
| db.currentOp | N/A |
|
|
482
|
+
| db.dropDatabase | Yes |
|
|
483
|
+
| db.eval | N/A |
|
|
484
|
+
| db.fsyncLock | N/A |
|
|
485
|
+
| db.fsyncUnlock | N/A |
|
|
486
|
+
| db.getCollection | no |
|
|
487
|
+
| db.getCollectionInfos | no |
|
|
488
|
+
| db.getCollectionNames | Yes |
|
|
489
|
+
| db.getLastError | no |
|
|
490
|
+
| db.getLastErrorObj | no |
|
|
491
|
+
| db.getLogComponents | N/A |
|
|
492
|
+
| db.getMongo | N/A |
|
|
493
|
+
| db.getName | no |
|
|
494
|
+
| db.getPrevError | no |
|
|
495
|
+
| db.getProfilingLevel | N/A |
|
|
496
|
+
| db.getProfilingStatus | N/A |
|
|
497
|
+
| db.getReplicationInfo | N/A |
|
|
498
|
+
| db.getSiblingDB | N/A |
|
|
499
|
+
| db.help | Yes |
|
|
500
|
+
| db.hostInfo | N/A |
|
|
501
|
+
| db.isMaster | N/A |
|
|
502
|
+
| db.killOp | N/A |
|
|
503
|
+
| db.listCommands | N/A |
|
|
504
|
+
| db.loadServerScripts | N/A |
|
|
505
|
+
| db.printCollectionStats | N/A |
|
|
506
|
+
| db.printReplicationInfo | N/A |
|
|
507
|
+
| db.printShardingStatus | N/A |
|
|
508
|
+
| db.printSlaveReplicationInfo | N/A |
|
|
509
|
+
| db.repairDatabase | N/A |
|
|
510
|
+
| db.resetError | N/A |
|
|
511
|
+
| db.runCommand | N/A |
|
|
512
|
+
| db.serverBuildInfo | N/A |
|
|
513
|
+
| db.serverCmdLineOpts | N/A |
|
|
514
|
+
| db.serverStatus | N/A |
|
|
515
|
+
| db.setLogLevel | N/A |
|
|
516
|
+
| db.setProfilingLevel | N/A |
|
|
517
|
+
| db.shutdownServer | N/A |
|
|
518
|
+
| db.stats | no |
|
|
519
|
+
| db.version | no |
|
|
520
|
+
| db.upgradeCheck | N/A |
|
|
521
|
+
|
|
522
|
+
## Collection Methods
|
|
523
|
+
|
|
524
|
+
| Name | Implemented |
|
|
525
|
+
|------------------------------------|-------------|
|
|
526
|
+
| db.collection.aggregate | yes |
|
|
527
|
+
| db.collection.bulkWrite | no |
|
|
528
|
+
| db.collection.count | yes |
|
|
529
|
+
| db.collection.copyTo | yes |
|
|
530
|
+
| db.collection.createIndex | yes |
|
|
531
|
+
| db.collection.dataSize | no |
|
|
532
|
+
| db.collection.deleteOne | yes |
|
|
533
|
+
| db.collection.deleteMany | yes |
|
|
534
|
+
| db.collection.distinct | yes |
|
|
535
|
+
| db.collection.drop | yes |
|
|
536
|
+
| db.collection.dropIndex | no |
|
|
537
|
+
| db.collection.dropIndexes | no |
|
|
538
|
+
| db.collection.ensureIndex | no |
|
|
539
|
+
| db.collection.explain | no |
|
|
540
|
+
| db.collection.find | yes |
|
|
541
|
+
| db.collection.findAndModify | no |
|
|
542
|
+
| db.collection.findOne | yes |
|
|
543
|
+
| db.collection.findOneAndDelete | yes |
|
|
544
|
+
| db.collection.findOneAndReplace | yes |
|
|
545
|
+
| db.collection.findOneAndUpdate | yes |
|
|
546
|
+
| db.collection.getIndexes | yes |
|
|
547
|
+
| db.collection.getShardDistribution | N/A |
|
|
548
|
+
| db.collection.getShardVersion | N/A |
|
|
549
|
+
| db.collection.group | no |
|
|
550
|
+
| db.collection.insert | yes |
|
|
551
|
+
| db.collection.insertOne | yes |
|
|
552
|
+
| db.collection.insertMany | yes |
|
|
553
|
+
| db.collection.isCapped | no |
|
|
554
|
+
| db.collection.mapReduce | no |
|
|
555
|
+
| db.collection.reIndex | no |
|
|
556
|
+
| db.collection.replaceOne | yes |
|
|
557
|
+
| db.collection.remove | yes |
|
|
558
|
+
| db.collection.renameCollection | no |
|
|
559
|
+
| db.collection.save | no |
|
|
560
|
+
| db.collection.stats | no |
|
|
561
|
+
| db.collection.storageSize | no |
|
|
562
|
+
| db.collection.totalSize | no |
|
|
563
|
+
| db.collection.totalIndexSize | no |
|
|
564
|
+
| db.collection.update | yes |
|
|
565
|
+
| db.collection.updateOne | yes |
|
|
566
|
+
| db.collection.updateMany | yes |
|
|
567
|
+
| db.collection.validate | no |
|
|
568
|
+
| db.collection.watch | yes |
|
|
569
|
+
|
|
570
|
+
## Change Streams
|
|
571
|
+
|
|
572
|
+
**NEW:** BabyMongo supports change streams for reactive programming! Watch for real-time data changes at the collection, database, or client level.
|
|
573
|
+
|
|
574
|
+
```javascript
|
|
575
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
576
|
+
|
|
577
|
+
async function main() {
|
|
578
|
+
const bridge = await WorkerBridge.create();
|
|
579
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
580
|
+
workerBridge: bridge
|
|
581
|
+
});
|
|
582
|
+
await client.connect();
|
|
583
|
+
const db = client.db('myapp');
|
|
584
|
+
const collection = db.collection('users');
|
|
585
|
+
|
|
586
|
+
// Watch for changes to a collection (synchronous, returns immediately)
|
|
587
|
+
const changeStream = collection.watch();
|
|
588
|
+
|
|
589
|
+
changeStream.on('change', (change) => {
|
|
590
|
+
console.log('Change detected:', change.operationType);
|
|
591
|
+
console.log('Document:', change.fullDocument);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Make changes - they'll trigger the change stream
|
|
595
|
+
await collection.insertOne({ name: 'Alice', age: 30 });
|
|
596
|
+
await collection.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
|
|
597
|
+
await collection.deleteOne({ name: 'Alice' });
|
|
598
|
+
|
|
599
|
+
// Filter changes using aggregation pipelines (also synchronous)
|
|
600
|
+
const filtered = collection.watch([
|
|
601
|
+
{ $match: { 'fullDocument.age': { $gte: 30 } } }
|
|
602
|
+
]);
|
|
603
|
+
|
|
604
|
+
// Watch at database level (async, requires await)
|
|
605
|
+
const dbStream = await db.watch();
|
|
606
|
+
|
|
607
|
+
// Close when done
|
|
608
|
+
changeStream.close();
|
|
609
|
+
|
|
610
|
+
await client.close();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
main().catch(console.error);
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
See [CHANGE-STREAMS.md](CHANGE-STREAMS.md) for complete documentation, examples, and browser reactivity patterns.
|
|
617
|
+
|
|
618
|
+
## Filtered Positional Operator with arrayFilters
|
|
619
|
+
|
|
620
|
+
**NEW:** BabyMongo supports the filtered positional operator `$[<identifier>]` with `arrayFilters`, allowing you to update specific array elements that match filter conditions!
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
import { MongoClient, WorkerBridge } from 'babymongo';
|
|
624
|
+
|
|
625
|
+
async function main() {
|
|
626
|
+
const bridge = await WorkerBridge.create();
|
|
627
|
+
const client = new MongoClient('mongodb://localhost:27017/myapp', {
|
|
628
|
+
workerBridge: bridge
|
|
629
|
+
});
|
|
630
|
+
await client.connect();
|
|
631
|
+
const db = client.db('myapp');
|
|
632
|
+
|
|
633
|
+
// Update items with quantity <= 5
|
|
634
|
+
await db.products.insertOne({
|
|
635
|
+
_id: 1,
|
|
636
|
+
items: [
|
|
637
|
+
{ name: 'apple', quantity: 5 },
|
|
638
|
+
{ name: 'banana', quantity: 0 },
|
|
639
|
+
{ name: 'orange', quantity: 10 }
|
|
640
|
+
]
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
await db.products.updateOne(
|
|
644
|
+
{ _id: 1 },
|
|
645
|
+
{ $set: { 'items.$[elem].quantity': 100 } },
|
|
646
|
+
{ arrayFilters: [{ 'elem.quantity': { $lte: 5 } }] }
|
|
647
|
+
);
|
|
648
|
+
// Result: apple and banana quantity set to 100, orange remains 10
|
|
649
|
+
|
|
650
|
+
// Update simple arrays
|
|
651
|
+
await db.scores.updateOne(
|
|
652
|
+
{ _id: 1 },
|
|
653
|
+
{ $set: { 'scores.$[score]': 90 } },
|
|
654
|
+
{ arrayFilters: [{ 'score': { $lt: 90 } }] }
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
// Nested arrays with multiple filters
|
|
658
|
+
await db.students.updateOne(
|
|
659
|
+
{ _id: 1 },
|
|
660
|
+
{ $inc: { 'students.$[student].grades.$[grade].score': 5 } },
|
|
661
|
+
{
|
|
662
|
+
arrayFilters: [
|
|
663
|
+
{ 'student.name': 'Alice' },
|
|
664
|
+
{ 'grade.score': { $lt: 90 } }
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
await client.close();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
main().catch(console.error);
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
See [docs/ARRAY-FILTERS.md](docs/ARRAY-FILTERS.md) for complete documentation and examples.
|
|
676
|
+
|
|
677
|
+
## Cursor Methods
|
|
678
|
+
|
|
679
|
+
| Name | Implemented |
|
|
680
|
+
|-------------------------|-----------------|
|
|
681
|
+
| cursor.batchSize | N/A |
|
|
682
|
+
| cursor.close | N/A |
|
|
683
|
+
| cursor.comment | no |
|
|
684
|
+
| cursor.count | yes |
|
|
685
|
+
| cursor.explain | N/A |
|
|
686
|
+
| cursor.forEach | yes |
|
|
687
|
+
| cursor.hasNext | yes |
|
|
688
|
+
| cursor.hint | N/A |
|
|
689
|
+
| cursor.itcount | no |
|
|
690
|
+
| cursor.limit | yes |
|
|
691
|
+
| cursor.map | yes |
|
|
692
|
+
| cursor.maxScan | N/A |
|
|
693
|
+
| cursor.maxTimeMS | N/A |
|
|
694
|
+
| cursor.max | no |
|
|
695
|
+
| cursor.min | no |
|
|
696
|
+
| cursor.next | yes |
|
|
697
|
+
| cursor.noCursorTimeout | N/A |
|
|
698
|
+
| cursor.objsLeftInBatch | N/A |
|
|
699
|
+
| cursor.pretty | no |
|
|
700
|
+
| cursor.readConcern | N/A |
|
|
701
|
+
| cursor.readPref | N/A |
|
|
702
|
+
| cursor.returnKey | N/A |
|
|
703
|
+
| cursor.showRecordId | N/A |
|
|
704
|
+
| cursor.size | no |
|
|
705
|
+
| cursor.skip | yes |
|
|
706
|
+
| cursor.snapshot | no |
|
|
707
|
+
| cursor.sort | yes |
|
|
708
|
+
| cursor.tailable | no |
|
|
709
|
+
| cursor.toArray | yes |
|
|
710
|
+
| cursor.next() | yes |
|
|
711
|
+
|
|
712
|
+
|