mongolite-ts 0.6.2 → 0.8.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 +97 -586
- package/dist/adapters/browser.d.ts +97 -0
- package/dist/adapters/browser.js +103 -0
- package/dist/adapters/browser.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +106 -0
- package/dist/adapters/cloudflare.js +94 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/changeStream.d.ts +2 -2
- package/dist/changeStream.js.map +1 -1
- package/dist/cloudflare.d.ts +39 -0
- package/dist/cloudflare.js +39 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/collection.d.ts +93 -6
- package/dist/collection.js +796 -6
- package/dist/collection.js.map +1 -1
- package/dist/cursors/findCursor.d.ts +19 -4
- package/dist/cursors/findCursor.js +136 -2
- package/dist/cursors/findCursor.js.map +1 -1
- package/dist/db.d.ts +16 -1
- package/dist/db.js +26 -0
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +17 -38
- package/dist/index.js +26 -46
- package/dist/index.js.map +1 -1
- package/dist/mongo-client.d.ts +56 -0
- package/dist/mongo-client.js +55 -0
- package/dist/mongo-client.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/package.json +17 -6
- package/dist/database-manager.d.ts +0 -1
- package/dist/database-manager.js +0 -2
- package/dist/database-manager.js.map +0 -1
- package/dist/document-utils.d.ts +0 -34
- package/dist/document-utils.js +0 -101
- package/dist/document-utils.js.map +0 -1
- package/dist/find-cursor.d.ts +0 -51
- package/dist/find-cursor.js +0 -204
- package/dist/find-cursor.js.map +0 -1
- package/dist/plugins/mongodbSync.d.ts +0 -128
- package/dist/plugins/mongodbSync.js +0 -339
- package/dist/plugins/mongodbSync.js.map +0 -1
- package/dist/query-builder.d.ts +0 -18
- package/dist/query-builder.js +0 -358
- package/dist/query-builder.js.map +0 -1
- package/dist/update-operations.d.ts +0 -17
- package/dist/update-operations.js +0 -147
- package/dist/update-operations.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,653 +3,164 @@
|
|
|
3
3
|
[](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/mongolite-ts)
|
|
5
5
|
[](https://codecov.io/gh/semics-tech/mongolite)
|
|
6
|
+
[](./LICENSE)
|
|
6
7
|
|
|
7
|
-
A MongoDB-like client
|
|
8
|
+
A MongoDB-like client backed by SQLite. Use a familiar MongoDB API with the simplicity of a local file-based database — no server required.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
* MongoDB-like API (`insertOne`, `findOne`, `updateOne`, `deleteOne`, etc.)
|
|
12
|
-
* SQLite backend for persistence.
|
|
13
|
-
* Automatic `_id` (UUID) generation and indexing.
|
|
14
|
-
* Support for Write Ahead Logging (WAL) mode for better concurrency.
|
|
15
|
-
* Support for querying JSON fields.
|
|
16
|
-
* Written in TypeScript with strong typing.
|
|
17
|
-
* 100% test coverage (aiming for).
|
|
18
|
-
* **Interactive Query Debugger** - Debug complex queries with `npx mongolite-debug`
|
|
19
|
-
* **JSON Safety & Data Integrity** - Comprehensive protection against malformed JSON and data corruption
|
|
20
|
-
* **Change Streams** - Real-time change tracking similar to MongoDB's `changeStream = collection.watch()`
|
|
21
|
-
|
|
22
|
-
## JSON Safety & Data Integrity
|
|
23
|
-
|
|
24
|
-
MongoLite includes robust safeguards to prevent and handle malformed JSON data that could cause application failures or data corruption:
|
|
25
|
-
|
|
26
|
-
### Document Validation
|
|
27
|
-
- **Pre-storage validation**: Automatically validates documents before insertion to prevent storing invalid data
|
|
28
|
-
- **Type safety**: Rejects non-JSON-serializable data (functions, symbols, BigInt, RegExp, circular references)
|
|
29
|
-
- **Round-trip verification**: Ensures all data can be safely stored and retrieved
|
|
10
|
+
## Why MongoLite?
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- **Fallback objects**: Returns special marker objects for unrecoverable data with debugging information
|
|
35
|
-
- **Error logging**: Detailed logging for debugging and monitoring data integrity issues
|
|
36
|
-
|
|
37
|
-
### Example Usage
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
// Document validation prevents invalid data
|
|
41
|
-
try {
|
|
42
|
-
await collection.insertOne({
|
|
43
|
-
name: 'user',
|
|
44
|
-
invalidFunction: () => 'not allowed' // This will be rejected
|
|
45
|
-
});
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.log('Validation error:', error.message);
|
|
48
|
-
// "Cannot insert document: Document validation failed: Functions are not allowed in documents"
|
|
49
|
-
}
|
|
12
|
+
- You want a MongoDB-style API without running a MongoDB server
|
|
13
|
+
- You need a lightweight, embedded database for local apps, CLIs, or testing
|
|
14
|
+
- You want simple file-based persistence with zero infrastructure overhead
|
|
50
15
|
|
|
51
|
-
|
|
52
|
-
const doc = await collection.findOne({ _id: 'some-id' });
|
|
53
|
-
if (doc && '__mongoLiteCorrupted' in doc) {
|
|
54
|
-
console.log('Found corrupted document');
|
|
55
|
-
console.log('Original data:', doc.__originalData);
|
|
56
|
-
console.log('Error details:', doc.__error);
|
|
57
|
-
// Handle corruption appropriately
|
|
58
|
-
}
|
|
59
|
-
```
|
|
16
|
+
## Features
|
|
60
17
|
|
|
61
|
-
|
|
18
|
+
- **MongoDB-compatible API** — `insertOne`, `findOne`, `updateOne`, `deleteOne`, `find`, `aggregate`, and more
|
|
19
|
+
- **SQLite persistence** — single file, zero configuration, works offline
|
|
20
|
+
- **Automatic `_id` generation** — UUID assigned on insert if not provided
|
|
21
|
+
- **WAL mode** — Write-Ahead Logging for better concurrent read access
|
|
22
|
+
- **Rich query operators** — `$eq`, `$gt`, `$in`, `$and`, `$or`, `$elemMatch`, `$regex`, and more
|
|
23
|
+
- **Update operators** — `$set`, `$inc`, `$push`, `$pull`, `$addToSet`, `$mul`, and more
|
|
24
|
+
- **Indexing** — create, list, and drop indexes including unique and compound indexes
|
|
25
|
+
- **Change streams** — real-time change tracking via `collection.watch()`
|
|
26
|
+
- **JSON safety** — validates documents before insert and recovers from corrupted data
|
|
27
|
+
- **TypeScript** — fully typed with strict mode
|
|
62
28
|
|
|
63
29
|
## Installation
|
|
64
30
|
|
|
65
31
|
```bash
|
|
66
32
|
npm install mongolite-ts
|
|
67
|
-
# or
|
|
68
|
-
yarn add mongolite-ts
|
|
69
33
|
```
|
|
70
34
|
|
|
71
|
-
##
|
|
35
|
+
## Quick Start
|
|
72
36
|
|
|
73
37
|
```typescript
|
|
74
38
|
import { MongoLite } from 'mongolite-ts';
|
|
75
|
-
import path from 'path';
|
|
76
|
-
|
|
77
|
-
interface User {
|
|
78
|
-
_id?: string;
|
|
79
|
-
name: string;
|
|
80
|
-
age: number;
|
|
81
|
-
email: string;
|
|
82
|
-
address?: {
|
|
83
|
-
street: string;
|
|
84
|
-
city: string;
|
|
85
|
-
};
|
|
86
|
-
hobbies?: string[];
|
|
87
|
-
}
|
|
88
39
|
|
|
89
40
|
async function main() {
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
const dbPath = path.join(__dirname, 'mydatabase.sqlite');
|
|
93
|
-
const client = new MongoLite(dbPath);
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
// Connect to the database (optional, operations will auto-connect)
|
|
97
|
-
await client.connect();
|
|
98
|
-
console.log('Connected to SQLite database.');
|
|
99
|
-
|
|
100
|
-
// Get a collection (equivalent to a table)
|
|
101
|
-
const usersCollection = client.collection<User>('users');
|
|
102
|
-
|
|
103
|
-
// Insert a document
|
|
104
|
-
const insertResult = await usersCollection.insertOne({
|
|
105
|
-
name: 'Alice Wonderland',
|
|
106
|
-
age: 30,
|
|
107
|
-
email: 'alice@example.com',
|
|
108
|
-
address: { street: '123 Main St', city: 'Anytown' },
|
|
109
|
-
hobbies: ['reading', 'coding']
|
|
110
|
-
});
|
|
111
|
-
console.log('Inserted user:', insertResult);
|
|
112
|
-
const userId = insertResult.insertedId;
|
|
113
|
-
|
|
114
|
-
// Find a document
|
|
115
|
-
const foundUser = await usersCollection.findOne({ _id: userId });
|
|
116
|
-
console.log('Found user by ID:', foundUser);
|
|
117
|
-
|
|
118
|
-
const foundUserByEmail = await usersCollection.findOne({ email: 'alice@example.com' });
|
|
119
|
-
console.log('Found user by email:', foundUserByEmail);
|
|
120
|
-
|
|
121
|
-
// Find with nested query
|
|
122
|
-
const foundUserByCity = await usersCollection.findOne({ 'address.city': 'Anytown' });
|
|
123
|
-
console.log('Found user by city:', foundUserByCity);
|
|
124
|
-
|
|
125
|
-
// Find with operator ($gt)
|
|
126
|
-
const olderUsers = await usersCollection.find({ age: { $gt: 25 } }).toArray();
|
|
127
|
-
console.log('Users older than 25:', olderUsers);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Update a document
|
|
131
|
-
const updateResult = await usersCollection.updateOne(
|
|
132
|
-
{ _id: userId },
|
|
133
|
-
{ $set: { age: 31, 'address.street': '456 New St' }, $push: { hobbies: 'gardening' } }
|
|
134
|
-
);
|
|
135
|
-
console.log('Update result:', updateResult);
|
|
136
|
-
|
|
137
|
-
const updatedUser = await usersCollection.findOne({ _id: userId });
|
|
138
|
-
console.log('Updated user:', updatedUser);
|
|
139
|
-
|
|
140
|
-
// Delete a document
|
|
141
|
-
const deleteResult = await usersCollection.deleteOne({ _id: userId });
|
|
142
|
-
console.log('Delete result:', deleteResult);
|
|
143
|
-
|
|
144
|
-
const notFoundUser = await usersCollection.findOne({ _id: userId });
|
|
145
|
-
console.log('User after deletion (should be null):', notFoundUser);
|
|
146
|
-
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error('An error occurred:', error);
|
|
149
|
-
} finally {
|
|
150
|
-
// Close the database connection when done
|
|
151
|
-
await client.close();
|
|
152
|
-
console.log('Database connection closed.');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
main();
|
|
157
|
-
```
|
|
41
|
+
const client = new MongoLite('./myapp.sqlite');
|
|
42
|
+
// Use ':memory:' for an ephemeral in-memory database
|
|
158
43
|
|
|
159
|
-
|
|
44
|
+
const users = client.collection('users');
|
|
160
45
|
|
|
161
|
-
|
|
46
|
+
// Insert
|
|
47
|
+
const result = await users.insertOne({ name: 'Alice', age: 30 });
|
|
162
48
|
|
|
163
|
-
|
|
49
|
+
// Find
|
|
50
|
+
const user = await users.findOne({ name: 'Alice' });
|
|
164
51
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const client = new MongoLite('./mydatabase.sqlite');
|
|
169
|
-
const collection = client.collection('users');
|
|
170
|
-
|
|
171
|
-
// Create a change stream
|
|
172
|
-
const changeStream = collection.watch({
|
|
173
|
-
fullDocument: 'updateLookup', // Include full document on updates
|
|
174
|
-
fullDocumentBeforeChange: 'whenAvailable' // Include document before change
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Listen for changes
|
|
178
|
-
changeStream.on('change', (change) => {
|
|
179
|
-
console.log('Change detected:', {
|
|
180
|
-
operation: change.operationType, // 'insert', 'update', 'delete'
|
|
181
|
-
documentId: change.documentKey._id,
|
|
182
|
-
collection: change.ns.coll,
|
|
183
|
-
timestamp: change.clusterTime
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
if (change.fullDocument) {
|
|
187
|
-
console.log('New document state:', change.fullDocument);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (change.updateDescription) {
|
|
191
|
-
console.log('Updated fields:', change.updateDescription.updatedFields);
|
|
192
|
-
console.log('Removed fields:', change.updateDescription.removedFields);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// Handle errors
|
|
197
|
-
changeStream.on('error', (error) => {
|
|
198
|
-
console.error('Change stream error:', error);
|
|
199
|
-
});
|
|
52
|
+
// Update
|
|
53
|
+
await users.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
|
|
200
54
|
|
|
201
|
-
//
|
|
202
|
-
await
|
|
203
|
-
await collection.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
|
|
204
|
-
await collection.deleteOne({ name: 'Alice' });
|
|
205
|
-
|
|
206
|
-
// Close the change stream when done
|
|
207
|
-
changeStream.close();
|
|
208
|
-
```
|
|
55
|
+
// Delete
|
|
56
|
+
await users.deleteOne({ name: 'Alice' });
|
|
209
57
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Change streams support async iteration for a more declarative approach:
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
const changeStream = collection.watch();
|
|
216
|
-
|
|
217
|
-
// Use async iteration
|
|
218
|
-
for await (const change of changeStream) {
|
|
219
|
-
console.log('Change detected:', change.operationType);
|
|
220
|
-
|
|
221
|
-
// Process the change...
|
|
222
|
-
|
|
223
|
-
// Break after processing some changes
|
|
224
|
-
if (someCondition) {
|
|
225
|
-
changeStream.close();
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
58
|
+
await client.close();
|
|
228
59
|
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Change Stream Options
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
interface ChangeStreamOptions {
|
|
235
|
-
// Filter to apply to change events
|
|
236
|
-
filter?: Filter<ChangeStreamDocument>;
|
|
237
|
-
|
|
238
|
-
// Whether to include the full document in insert and update operations
|
|
239
|
-
fullDocument?: 'default' | 'updateLookup' | 'whenAvailable' | 'required';
|
|
240
|
-
|
|
241
|
-
// Whether to include the full document before the change
|
|
242
|
-
fullDocumentBeforeChange?: 'off' | 'whenAvailable' | 'required';
|
|
243
|
-
|
|
244
|
-
// Maximum number of events to buffer
|
|
245
|
-
maxBufferSize?: number;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Example with options
|
|
249
|
-
const changeStream = collection.watch({
|
|
250
|
-
fullDocument: 'updateLookup',
|
|
251
|
-
fullDocumentBeforeChange: 'whenAvailable',
|
|
252
|
-
maxBufferSize: 500
|
|
253
|
-
});
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Change Document Structure
|
|
257
60
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
interface ChangeStreamDocument {
|
|
262
|
-
_id: string; // Unique change event ID
|
|
263
|
-
operationType: 'insert' | 'update' | 'delete' | 'replace';
|
|
264
|
-
clusterTime?: Date; // Timestamp of the change
|
|
265
|
-
fullDocument?: T; // Full document (based on options)
|
|
266
|
-
fullDocumentBeforeChange?: T; // Document before change (based on options)
|
|
267
|
-
documentKey: { _id: string }; // ID of the affected document
|
|
268
|
-
ns: { // Namespace information
|
|
269
|
-
db: string; // Database name
|
|
270
|
-
coll: string; // Collection name
|
|
271
|
-
};
|
|
272
|
-
updateDescription?: { // Update details (for update operations)
|
|
273
|
-
updatedFields: Record<string, unknown>;
|
|
274
|
-
removedFields: string[];
|
|
275
|
-
};
|
|
276
|
-
}
|
|
61
|
+
main();
|
|
277
62
|
```
|
|
278
63
|
|
|
279
|
-
|
|
64
|
+
## Documentation
|
|
280
65
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
66
|
+
| Topic | Description |
|
|
67
|
+
|-------|-------------|
|
|
68
|
+
| [API Reference](./docs/API.md) | Full API docs: methods, query operators, update operators |
|
|
69
|
+
| [Change Streams](./docs/CHANGE_STREAMS.md) | Real-time change tracking with `collection.watch()` |
|
|
70
|
+
| [JSON Safety](./docs/JSON_SAFETY.md) | Document validation and corrupted data recovery |
|
|
71
|
+
| [Query Debugger](./docs/DEBUGGER.md) | Interactive CLI for debugging queries and inspecting SQL |
|
|
72
|
+
| [Benchmarks](./docs/BENCHMARKS.md) | Performance benchmarks and storage characteristics |
|
|
73
|
+
| [Cloudflare Durable Objects](./docs/CLOUDFLARE.md) | Using MongoLite inside a Cloudflare Durable Object |
|
|
286
74
|
|
|
287
|
-
|
|
75
|
+
## Backend Examples
|
|
288
76
|
|
|
289
|
-
|
|
290
|
-
2. **Error Handling**: Implement proper error handling for change stream events
|
|
291
|
-
3. **Buffer Management**: Consider the `maxBufferSize` option for high-volume scenarios
|
|
292
|
-
4. **Cleanup**: Call `changeStream.cleanup()` to remove triggers if needed
|
|
77
|
+
### SQLite file (Node.js / Bun)
|
|
293
78
|
|
|
294
79
|
```typescript
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
const changeStream = collection.watch();
|
|
298
|
-
|
|
299
|
-
// ... use change stream
|
|
300
|
-
|
|
301
|
-
} finally {
|
|
302
|
-
changeStream.close();
|
|
303
|
-
await changeStream.cleanup(); // Remove triggers if needed
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## Query Debugger
|
|
308
|
-
|
|
309
|
-
MongoLite includes an interactive query debugger to help you debug complex queries and understand how MongoDB-style filters are converted to SQL.
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
# Start the debugger
|
|
313
|
-
npx mongolite-debug
|
|
314
|
-
|
|
315
|
-
# Use with your specific database
|
|
316
|
-
npx mongolite-debug -d ./path/to/your/database.db
|
|
80
|
+
import { MongoLite } from 'mongolite-ts';
|
|
317
81
|
|
|
318
|
-
|
|
319
|
-
|
|
82
|
+
const client = new MongoLite('./myapp.sqlite');
|
|
83
|
+
await client.connect();
|
|
84
|
+
const users = client.collection('users');
|
|
85
|
+
await users.insertOne({ name: 'Alice', age: 30 });
|
|
86
|
+
await client.close();
|
|
320
87
|
```
|
|
321
88
|
|
|
322
|
-
|
|
323
|
-
- Convert find queries to SQL and see the generated queries
|
|
324
|
-
- Execute raw SQL queries for testing
|
|
325
|
-
- Sample data from collections
|
|
326
|
-
- Compare MongoDB-style queries with optimized SQL
|
|
327
|
-
|
|
328
|
-
See [DEBUGGER.md](./docs/DEBUGGER.md) for detailed usage instructions and examples.
|
|
329
|
-
|
|
330
|
-
## API
|
|
331
|
-
|
|
332
|
-
### `MongoLite`
|
|
333
|
-
|
|
334
|
-
#### `constructor(dbPathOrOptions: string | MongoLiteOptions)`
|
|
335
|
-
|
|
336
|
-
Creates a new `MongoLite` client instance.
|
|
337
|
-
|
|
338
|
-
* `dbPathOrOptions`: Either a string path to the SQLite database file (e.g., `'./mydb.sqlite'`, `':memory:'`) or an options object.
|
|
339
|
-
* `MongoLiteOptions`:
|
|
340
|
-
* `filePath`: string - Path to the SQLite database file.
|
|
341
|
-
* `verbose?`: boolean - (Optional) Enable verbose logging from the `sqlite3` driver.
|
|
342
|
-
|
|
343
|
-
#### `async connect(): Promise<void>`
|
|
344
|
-
|
|
345
|
-
Explicitly opens the database connection. Operations will automatically connect if the DB is not already open.
|
|
346
|
-
|
|
347
|
-
#### `async close(): Promise<void>`
|
|
348
|
-
|
|
349
|
-
Closes the database connection.
|
|
350
|
-
|
|
351
|
-
#### `listCollections(): Promise<string[]>`
|
|
352
|
-
|
|
353
|
-
Lists all collections (tables) in the database.
|
|
89
|
+
### In-memory (tests / ephemeral)
|
|
354
90
|
|
|
355
91
|
```typescript
|
|
356
|
-
|
|
357
|
-
const collections = await client.listCollections().toArray();
|
|
358
|
-
console.log('Available collections:', collections);
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
#### `collection<T extends DocumentWithId = DocumentWithId>(name: string): MongoLiteCollection<T>`
|
|
362
|
-
|
|
363
|
-
Gets a reference to a collection (table).
|
|
364
|
-
|
|
365
|
-
* `name`: The name of the collection.
|
|
366
|
-
* Returns a `MongoLiteCollection` instance.
|
|
367
|
-
|
|
368
|
-
### `MongoLiteCollection<T extends DocumentWithId>`
|
|
369
|
-
|
|
370
|
-
Represents a collection and provides methods to interact with its documents. `T` is a generic type for your document structure, which must extend `DocumentWithId` (i.e., have an optional `_id: string` field).
|
|
371
|
-
|
|
372
|
-
#### `async insertOne(doc: Omit<T, '_id'> & { _id?: string }): Promise<InsertOneResult>`
|
|
373
|
-
|
|
374
|
-
Inserts a single document into the collection. If `_id` is not provided, a UUID will be generated.
|
|
375
|
-
|
|
376
|
-
* `doc`: The document to insert.
|
|
377
|
-
* Returns `InsertOneResult`: `{ acknowledged: boolean; insertedId: string; }`.
|
|
378
|
-
|
|
379
|
-
#### `async findOne(filter: Filter<T>): Promise<T | null>`
|
|
380
|
-
|
|
381
|
-
Finds a single document matching the filter.
|
|
382
|
-
|
|
383
|
-
* `filter`: The query criteria. Supports direct field matching (e.g., `{ name: 'Alice' }`) and nested field matching using dot notation (e.g., `{ 'address.city': 'Anytown' }`). Also supports operators like `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`.
|
|
384
|
-
* Returns the found document or `null`.
|
|
385
|
-
|
|
386
|
-
#### `find(filter: Filter<T>): FindCursor<T>`
|
|
387
|
-
|
|
388
|
-
Finds multiple documents matching the filter and returns a cursor.
|
|
389
|
-
|
|
390
|
-
* `filter`: The query criteria.
|
|
391
|
-
* Returns a `FindCursor` instance.
|
|
392
|
-
|
|
393
|
-
#### `async updateOne(filter: Filter<T>, update: UpdateFilter<T>): Promise<UpdateResult>`
|
|
394
|
-
|
|
395
|
-
Updates a single document matching the filter.
|
|
396
|
-
|
|
397
|
-
* `filter`: The selection criteria for the update.
|
|
398
|
-
* `update`: The modifications to apply. Supports operators like `$set`, `$unset`, `$inc`, `$push`, `$pull`.
|
|
399
|
-
* Returns `UpdateResult`: `{ acknowledged: boolean; matchedCount: number; modifiedCount: number; upsertedId: string | null; }`.
|
|
400
|
-
|
|
401
|
-
#### `async deleteOne(filter: Filter<T>): Promise<DeleteResult>`
|
|
402
|
-
|
|
403
|
-
Deletes a single document matching the filter.
|
|
404
|
-
|
|
405
|
-
* `filter`: The selection criteria for the deletion.
|
|
406
|
-
* Returns `DeleteResult`: `{ acknowledged: boolean; deletedCount: number; }`.
|
|
92
|
+
import { MongoLite } from 'mongolite-ts';
|
|
407
93
|
|
|
408
|
-
|
|
94
|
+
const client = new MongoLite(':memory:');
|
|
95
|
+
await client.connect();
|
|
96
|
+
const users = client.collection('users');
|
|
97
|
+
await users.insertOne({ name: 'Alice', age: 30 });
|
|
98
|
+
// Data is discarded when the process exits
|
|
99
|
+
await client.close();
|
|
100
|
+
```
|
|
409
101
|
|
|
410
|
-
|
|
102
|
+
### Browser (via sql.js)
|
|
411
103
|
|
|
412
|
-
|
|
413
|
-
* `fullDocument`: Controls when to include the full document ('default', 'updateLookup', 'whenAvailable', 'required')
|
|
414
|
-
* `fullDocumentBeforeChange`: Controls when to include the document before change ('off', 'whenAvailable', 'required')
|
|
415
|
-
* `maxBufferSize`: Maximum number of events to buffer (default: 1000)
|
|
416
|
-
* Returns a `ChangeStream` instance that extends EventEmitter and supports async iteration.
|
|
104
|
+
Requires [sql.js](https://www.npmjs.com/package/sql.js) (`npm install sql.js`).
|
|
417
105
|
|
|
418
106
|
```typescript
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
changeStream.on('change', (change) => {
|
|
422
|
-
console.log('Change detected:', change);
|
|
423
|
-
});
|
|
107
|
+
import initSqlJs from 'sql.js';
|
|
108
|
+
import { MongoLite, BrowserSqliteAdapter } from 'mongolite-ts';
|
|
424
109
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
fullDocument: 'updateLookup',
|
|
428
|
-
fullDocumentBeforeChange: 'whenAvailable'
|
|
110
|
+
const SQL = await initSqlJs({
|
|
111
|
+
locateFile: (file) => `https://cdn.jsdelivr.net/npm/sql.js/dist/${file}`,
|
|
429
112
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
113
|
+
const sqlJsDb = new SQL.Database(); // in-memory; use OPFS/IndexedDB for persistence
|
|
114
|
+
|
|
115
|
+
const client = new MongoLite(new BrowserSqliteAdapter(sqlJsDb));
|
|
116
|
+
await client.connect();
|
|
117
|
+
const users = client.collection('users');
|
|
118
|
+
await users.insertOne({ name: 'Alice', age: 30 });
|
|
119
|
+
console.log(await users.findOne({ name: 'Alice' }));
|
|
120
|
+
await client.close();
|
|
438
121
|
```
|
|
439
122
|
|
|
440
|
-
|
|
441
|
-
### `FindCursor<T>`
|
|
442
|
-
|
|
443
|
-
#### `async toArray(): Promise<T[]>`
|
|
444
|
-
|
|
445
|
-
Fetches all documents matching the cursor's query into an array.
|
|
446
|
-
|
|
447
|
-
#### `limit(count: number): FindCursor<T>`
|
|
448
|
-
|
|
449
|
-
Specifies the maximum number of documents the cursor will return.
|
|
450
|
-
|
|
451
|
-
#### `skip(count: number): FindCursor<T>`
|
|
452
|
-
|
|
453
|
-
Specifies the number of documents to skip.
|
|
454
|
-
|
|
455
|
-
#### `sort(sortCriteria: SortCriteria<T>): FindCursor<T>`
|
|
456
|
-
|
|
457
|
-
Specifies the sorting order.
|
|
458
|
-
* `sortCriteria`: An object where keys are field paths (dot notation for nested fields) and values are `1` (ascending) or `-1` (descending). Example: `{ 'age': -1, 'name': 1 }`.
|
|
459
|
-
|
|
460
|
-
## Query Operators
|
|
461
|
-
|
|
462
|
-
The `filter` parameter in `findOne`, `find`, `updateOne`, and `deleteOne` supports the following:
|
|
463
|
-
|
|
464
|
-
### Comparison Operators
|
|
465
|
-
|
|
466
|
-
* `{ field: value }` or `{ field: { $eq: value } }`: Matches documents where `field` equals `value`.
|
|
467
|
-
* `{ field: { $ne: value } }`: Matches documents where `field` does not equal `value`.
|
|
468
|
-
* `{ field: { $gt: value } }`: Matches documents where `field` is greater than `value`.
|
|
469
|
-
* `{ field: { $gte: value } }`: Matches documents where `field` is greater than or equal to `value`.
|
|
470
|
-
* `{ field: { $lt: value } }`: Matches documents where `field` is less than `value`.
|
|
471
|
-
* `{ field: { $lte: value } }`: Matches documents where `field` is less than or equal to `value`.
|
|
472
|
-
* `{ field: { $in: [value1, value2, ...] } }`: Matches documents where `field` is one of the specified values.
|
|
473
|
-
* `{ field: { $nin: [value1, value2, ...] } }`: Matches documents where `field` is not one of the specified values.
|
|
474
|
-
|
|
475
|
-
### Logical Operators (Top-Level Only for now)
|
|
476
|
-
|
|
477
|
-
* `{ $and: [filter1, filter2, ...] }`: Joins query clauses with a logical AND.
|
|
478
|
-
* `{ $or: [filter1, filter2, ...] }`: Joins query clauses with a logical OR.
|
|
479
|
-
* `{ $nor: [filter1, filter2, ...] }`: Joins query clauses with a logical NOR.
|
|
480
|
-
* `{ $not: filter }`: Inverts the effect of a query expression. (Applied to a single operator expression, e.g., `{ age: { $not: { $gt: 30 } } }`)
|
|
481
|
-
|
|
482
|
-
### Element Operators
|
|
483
|
-
|
|
484
|
-
* `{ field: { $exists: boolean } }`: Matches documents that have (or do not have) the specified field.
|
|
485
|
-
|
|
486
|
-
## Update Operators
|
|
487
|
-
|
|
488
|
-
The `update` parameter in `updateOne` supports the following:
|
|
489
|
-
|
|
490
|
-
* `{ $set: { field1: value1, ... } }`: Sets the value of specified fields.
|
|
491
|
-
* `{ $unset: { field1: "", ... } }`: Removes specified fields from documents.
|
|
492
|
-
* `{ $inc: { field1: amount1, ... } }`: Increments the value of specified fields by a certain amount.
|
|
493
|
-
* `{ $push: { arrayField: valueOrModifier, ... } }`: Appends a value to an array. Can use with `$each`.
|
|
494
|
-
* `{ $push: { scores: 89 } }`
|
|
495
|
-
* `{ $push: { scores: { $each: [90, 92, 85] } } }`
|
|
496
|
-
* `{ $pull: { arrayField: valueOrCondition, ... } }`: Removes all instances of a value or values that match a condition from an array.
|
|
497
|
-
* `{ $pull: { scores: 0 } }`
|
|
498
|
-
* `{ $pull: { items: { id: { $in: [1, 2] } } } }` (More complex conditions for $pull might be limited initially)
|
|
499
|
-
#### `async createIndex(fieldOrSpec: string | IndexSpecification, options?: CreateIndexOptions): Promise<CreateIndexResult>`
|
|
500
|
-
|
|
501
|
-
Creates an index on the specified field(s) of the collection.
|
|
502
|
-
|
|
503
|
-
* `fieldOrSpec`: Either a string field name or an index specification object (e.g., `{ name: 1, age: -1 }`).
|
|
504
|
-
* `options`: Optional settings for the index creation.
|
|
505
|
-
* `unique`: If `true`, the index will enforce uniqueness of the indexed field.
|
|
506
|
-
* `name`: Custom name for the index. If not provided, a name will be generated automatically.
|
|
507
|
-
* `background`: If `true`, creates the index in the background (in SQLite, this might not have a significant effect).
|
|
508
|
-
* `sparse`: If `true`, ignores documents that don't have the indexed field (for MongoDB compatibility).
|
|
509
|
-
* Returns `CreateIndexResult`: `{ acknowledged: boolean; name: string; }`.
|
|
123
|
+
### Cloudflare Durable Objects
|
|
510
124
|
|
|
511
125
|
```typescript
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// Create a unique index on the "email" field
|
|
516
|
-
await usersCollection.createIndex({ email: 1 }, { unique: true });
|
|
517
|
-
|
|
518
|
-
// Create a compound index on multiple fields
|
|
519
|
-
await usersCollection.createIndex({ name: 1, age: -1 });
|
|
520
|
-
|
|
521
|
-
// You can even create an index on a nested field
|
|
522
|
-
await usersCollection.createIndex({ 'address.city': 1 });
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
#### `listIndexes(): { toArray: () => Promise<IndexInfo[]> }`
|
|
526
|
-
|
|
527
|
-
Lists all indexes on the collection.
|
|
126
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
127
|
+
import { MongoLite, CloudflareDurableObjectAdapter } from 'mongolite-ts/cloudflare';
|
|
528
128
|
|
|
529
|
-
|
|
530
|
-
|
|
129
|
+
export class MyDurableObject extends DurableObject {
|
|
130
|
+
private client: MongoLite;
|
|
531
131
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
Drops a specified index from the collection.
|
|
540
|
-
|
|
541
|
-
* `indexName`: The name of the index to drop.
|
|
542
|
-
* Returns `DropIndexResult`: `{ acknowledged: boolean; name: string; }`.
|
|
543
|
-
|
|
544
|
-
#### `async dropIndexes(): Promise<{ acknowledged: boolean; droppedCount: number }>`
|
|
545
|
-
|
|
546
|
-
Drops all indexes from the collection, except for the index on _id.
|
|
547
|
-
|
|
548
|
-
* Returns an object with `acknowledged` (boolean) and `droppedCount` (number) indicating how many indexes were dropped.
|
|
549
|
-
|
|
550
|
-
## Performance Benchmarks
|
|
551
|
-
|
|
552
|
-
*Last updated: 2025-07-27*
|
|
553
|
-
|
|
554
|
-
### Operation Performance
|
|
555
|
-
|
|
556
|
-
| Operation | Records Tested | Duration (ms) | Ops/Second | Avg Time/Op (ms) |
|
|
557
|
-
|-----------|----------------|---------------|------------|------------------|
|
|
558
|
-
| INSERT_INDIVIDUAL | 1,000 | 4704.68 | 213 | 4.705 |
|
|
559
|
-
| INSERT_BATCH | 9,000 | 247.67 | 36339 | 0.028 |
|
|
560
|
-
| QUERY_SIMPLE_NO_INDEX | 1,000 | 72.35 | 13821 | 0.072 |
|
|
561
|
-
| QUERY_COMPLEX_NO_INDEX | 500 | 37.84 | 13214 | 0.076 |
|
|
562
|
-
| QUERY_ARRAY_NO_INDEX | 500 | 29.82 | 16768 | 0.060 |
|
|
563
|
-
| QUERY_FIND_MANY_NO_INDEX | 100 | 36.71 | 2724 | 0.367 |
|
|
564
|
-
| QUERY_SIMPLE_INDEXED | 1,000 | 48.70 | 20533 | 0.049 |
|
|
565
|
-
| QUERY_COMPLEX_INDEXED | 500 | 36.09 | 13854 | 0.072 |
|
|
566
|
-
| QUERY_ARRAY_INDEXED | 500 | 28.26 | 17692 | 0.057 |
|
|
567
|
-
| QUERY_FIND_MANY_INDEXED | 100 | 41.85 | 2390 | 0.418 |
|
|
568
|
-
| UPDATE | 1,000 | 6093.94 | 164 | 6.094 |
|
|
569
|
-
| DELETE | 1,000 | 17230.04 | 58 | 17.230 |
|
|
570
|
-
|
|
571
|
-
### Storage Capacity
|
|
572
|
-
|
|
573
|
-
| Records | Database Size (MB) | Avg Record Size (bytes) |
|
|
574
|
-
|---------|-------------------|------------------------|
|
|
575
|
-
| 1,000 | 0.44 | 459 |
|
|
576
|
-
| 10,000 | 4.27 | 448 |
|
|
577
|
-
| 50,000 | 21.36 | 448 |
|
|
578
|
-
| 100,000 | 42.75 | 448 |
|
|
579
|
-
|
|
580
|
-
### Notes
|
|
581
|
-
|
|
582
|
-
- **INSERT_INDIVIDUAL**: Individual insertOne() operations
|
|
583
|
-
- **INSERT_BATCH**: Batch insertions (insertMany or chunked Promise.all)
|
|
584
|
-
- **QUERY_SIMPLE_NO_INDEX**: Single field queries without indexes
|
|
585
|
-
- **QUERY_SIMPLE_INDEXED**: Single field queries with indexes
|
|
586
|
-
- **QUERY_COMPLEX_NO_INDEX**: Multi-field queries without indexes
|
|
587
|
-
- **QUERY_COMPLEX_INDEXED**: Multi-field queries with indexes
|
|
588
|
-
- **QUERY_ARRAY_NO_INDEX**: Array field queries without indexes
|
|
589
|
-
- **QUERY_ARRAY_INDEXED**: Array field queries with indexes
|
|
590
|
-
- **QUERY_FIND_MANY_NO_INDEX**: Batch queries returning up to 100 records without indexes
|
|
591
|
-
- **QUERY_FIND_MANY_INDEXED**: Batch queries returning up to 100 records with indexes
|
|
592
|
-
- **UPDATE**: Individual updateOne() operations with $set
|
|
593
|
-
- **DELETE**: Individual deleteOne() operations
|
|
594
|
-
|
|
595
|
-
**Performance Optimizations:**
|
|
596
|
-
- Batch inserts provide significant performance improvements over individual inserts
|
|
597
|
-
- Indexes dramatically improve query performance for filtered operations
|
|
598
|
-
- Complex queries benefit most from appropriate indexing
|
|
599
|
-
- Array field queries can be optimized with proper index strategies
|
|
600
|
-
|
|
601
|
-
**Storage Characteristics:**
|
|
602
|
-
- SQLite databases scale well with MongoLite
|
|
603
|
-
- Average record size includes JSON overhead and SQLite indexing
|
|
604
|
-
- Practical limits depend on available disk space and memory
|
|
605
|
-
- WAL mode provides better concurrent access for larger datasets
|
|
606
|
-
- Indexes add storage overhead but provide query performance benefits
|
|
607
|
-
|
|
608
|
-
**Recommendations:**
|
|
609
|
-
- Use batch operations for bulk data insertion
|
|
610
|
-
- Create indexes on frequently queried fields
|
|
611
|
-
- Monitor database file size growth with your specific data patterns
|
|
612
|
-
- Consider compound indexes for complex multi-field queries
|
|
132
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
133
|
+
super(ctx, env);
|
|
134
|
+
// Pass ctx.storage.sql — no file path needed
|
|
135
|
+
this.client = new MongoLite(new CloudflareDurableObjectAdapter(ctx.storage.sql));
|
|
136
|
+
ctx.blockConcurrencyWhile(() => this.client.collection('users').ensureTable());
|
|
137
|
+
}
|
|
613
138
|
|
|
614
|
-
|
|
139
|
+
async fetch(request: Request) {
|
|
140
|
+
const users = this.client.collection('users');
|
|
141
|
+
await users.insertOne({ name: 'Alice', age: 30 });
|
|
142
|
+
return Response.json(await users.findOne({ name: 'Alice' }));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
615
145
|
```
|
|
616
146
|
|
|
147
|
+
> See [docs/CLOUDFLARE.md](./docs/CLOUDFLARE.md) for the full guide, supported operations, and limitations.
|
|
617
148
|
|
|
618
149
|
## Development
|
|
619
150
|
|
|
620
|
-
1. Clone the repository.
|
|
621
|
-
2. Install dependencies: `npm install`
|
|
622
|
-
3. Build the project: `npm run build`
|
|
623
|
-
4. Run tests: `npm test`
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
### Build Verification
|
|
627
|
-
|
|
628
|
-
The package includes a comprehensive build verification system that ensures the compiled output works correctly in various contexts:
|
|
629
|
-
|
|
630
|
-
1. **Module Export Test**: Verifies that all classes and interfaces are correctly exported from the compiled module.
|
|
631
|
-
|
|
632
|
-
2. **Internal Module Resolution Test**: Ensures that all internal module imports work correctly in the compiled output.
|
|
633
|
-
|
|
634
|
-
3. **Third-Party Usage Test**: Simulates installing the package in a separate project to verify it works correctly when used as a dependency.
|
|
635
|
-
|
|
636
151
|
```bash
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
#
|
|
641
|
-
npm run
|
|
642
|
-
|
|
643
|
-
# Run all tests including linting, unit tests, and build verification
|
|
644
|
-
npm run test:all
|
|
152
|
+
git clone https://github.com/semics-tech/mongolite.git
|
|
153
|
+
cd mongolite
|
|
154
|
+
npm install
|
|
155
|
+
npm test # Run tests
|
|
156
|
+
npm run build # Compile TypeScript
|
|
157
|
+
npm run lint # Lint code
|
|
645
158
|
```
|
|
646
159
|
|
|
647
|
-
These tests help prevent common module resolution issues that can occur in ESM packages.
|
|
648
|
-
|
|
649
160
|
## Contributing
|
|
650
161
|
|
|
651
|
-
Contributions are welcome! Please
|
|
162
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting a pull request.
|
|
652
163
|
|
|
653
164
|
## License
|
|
654
165
|
|
|
655
|
-
MIT
|
|
166
|
+
[MIT](./LICENSE)
|