mongolite-ts 0.6.0 → 0.7.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 +46 -610
- package/dist/collection.d.ts +91 -4
- package/dist/collection.js +796 -6
- package/dist/collection.js.map +1 -1
- package/dist/cursors/findCursor.d.ts +17 -2
- package/dist/cursors/findCursor.js +139 -3
- package/dist/cursors/findCursor.js.map +1 -1
- package/dist/db.js +26 -0
- package/dist/db.js.map +1 -1
- package/dist/types.d.ts +109 -0
- package/package.json +8 -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,89 @@
|
|
|
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
|
|
30
|
-
|
|
31
|
-
### Malformed JSON Recovery
|
|
32
|
-
- **Graceful degradation**: Handles corrupted JSON data without crashing your application
|
|
33
|
-
- **Automatic recovery**: Attempts to fix common JSON corruption issues (escaped quotes, backslashes)
|
|
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
|
|
10
|
+
## Why MongoLite?
|
|
36
11
|
|
|
37
|
-
|
|
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
|
|
38
15
|
|
|
39
|
-
|
|
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
|
-
}
|
|
50
|
-
|
|
51
|
-
// Corrupted data recovery
|
|
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);
|
|
41
|
+
const client = new MongoLite('./myapp.sqlite');
|
|
42
|
+
// Use ':memory:' for an ephemeral in-memory database
|
|
117
43
|
|
|
118
|
-
|
|
119
|
-
console.log('Found user by email:', foundUserByEmail);
|
|
44
|
+
const users = client.collection('users');
|
|
120
45
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
console.log('Found user by city:', foundUserByCity);
|
|
46
|
+
// Insert
|
|
47
|
+
const result = await users.insertOne({ name: 'Alice', age: 30 });
|
|
124
48
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
console.log('Users older than 25:', olderUsers);
|
|
49
|
+
// Find
|
|
50
|
+
const user = await users.findOne({ name: 'Alice' });
|
|
128
51
|
|
|
52
|
+
// Update
|
|
53
|
+
await users.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
|
|
129
54
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{ _id: userId },
|
|
133
|
-
{ $set: { age: 31, 'address.street': '456 New St' }, $push: { hobbies: 'gardening' } }
|
|
134
|
-
);
|
|
135
|
-
console.log('Update result:', updateResult);
|
|
55
|
+
// Delete
|
|
56
|
+
await users.deleteOne({ name: 'Alice' });
|
|
136
57
|
|
|
137
|
-
|
|
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
|
-
}
|
|
58
|
+
await client.close();
|
|
154
59
|
}
|
|
155
60
|
|
|
156
61
|
main();
|
|
157
62
|
```
|
|
158
63
|
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
MongoLite supports real-time change tracking through change streams, similar to MongoDB's `collection.watch()` feature. Change streams allow you to monitor and react to data changes (inserts, updates, deletes) in real-time.
|
|
162
|
-
|
|
163
|
-
### Basic Usage
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
import { MongoLite } from 'mongolite-ts';
|
|
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
|
-
});
|
|
200
|
-
|
|
201
|
-
// Perform operations - changes will be captured
|
|
202
|
-
await collection.insertOne({ name: 'Alice', age: 30 });
|
|
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
|
-
```
|
|
209
|
-
|
|
210
|
-
### Async Iteration
|
|
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
|
-
}
|
|
228
|
-
}
|
|
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
|
-
|
|
258
|
-
Each change event contains detailed information about the operation:
|
|
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
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Implementation Details
|
|
280
|
-
|
|
281
|
-
- **SQLite Triggers**: Uses SQLite triggers to capture changes automatically
|
|
282
|
-
- **Change Log Table**: Stores change events in a dedicated `__mongolite_changes__` table
|
|
283
|
-
- **Polling**: Efficiently polls for new changes every 100ms
|
|
284
|
-
- **Cleanup**: Automatically cleans up triggers when change streams are closed
|
|
285
|
-
- **Error Handling**: Robust error handling for database operations and malformed data
|
|
286
|
-
|
|
287
|
-
### Best Practices
|
|
288
|
-
|
|
289
|
-
1. **Close Change Streams**: Always close change streams when done to free resources
|
|
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
|
|
293
|
-
|
|
294
|
-
```typescript
|
|
295
|
-
// Proper cleanup
|
|
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
|
|
317
|
-
|
|
318
|
-
# Start with a specific collection
|
|
319
|
-
npx mongolite-debug -c users --verbose
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
The debugger provides interactive commands to:
|
|
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.
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
// Get all collections
|
|
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; }`.
|
|
407
|
-
|
|
408
|
-
#### `watch(options?: ChangeStreamOptions<T>): ChangeStream<T>`
|
|
409
|
-
|
|
410
|
-
Opens a change stream to watch for changes on this collection. Returns a ChangeStream that emits events when documents are inserted, updated, or deleted.
|
|
411
|
-
|
|
412
|
-
* `options`: Optional configuration for the change stream
|
|
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.
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
// Basic change stream
|
|
420
|
-
const changeStream = collection.watch();
|
|
421
|
-
changeStream.on('change', (change) => {
|
|
422
|
-
console.log('Change detected:', change);
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// With options
|
|
426
|
-
const changeStream = collection.watch({
|
|
427
|
-
fullDocument: 'updateLookup',
|
|
428
|
-
fullDocumentBeforeChange: 'whenAvailable'
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// Async iteration
|
|
432
|
-
for await (const change of changeStream) {
|
|
433
|
-
console.log('Change:', change.operationType);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Always close when done
|
|
437
|
-
changeStream.close();
|
|
438
|
-
```
|
|
439
|
-
|
|
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; }`.
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
// Create a simple index on the "name" field
|
|
513
|
-
await usersCollection.createIndex({ name: 1 });
|
|
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.
|
|
528
|
-
|
|
529
|
-
* Returns an object with a `toArray()` method that resolves to an array of `IndexInfo` objects.
|
|
530
|
-
* Each `IndexInfo` contains: `name` (string), `key` (object mapping field names to sort direction), and `unique` (boolean).
|
|
531
|
-
|
|
532
|
-
```typescript
|
|
533
|
-
const indexes = await usersCollection.listIndexes().toArray();
|
|
534
|
-
console.log('Available indexes:', indexes);
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
#### `async dropIndex(indexName: string): Promise<DropIndexResult>`
|
|
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
|
|
613
|
-
|
|
614
|
-
## Development
|
|
615
|
-
```
|
|
64
|
+
## Documentation
|
|
616
65
|
|
|
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 |
|
|
617
73
|
|
|
618
74
|
## Development
|
|
619
75
|
|
|
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
76
|
```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
|
|
77
|
+
git clone https://github.com/semics-tech/mongolite.git
|
|
78
|
+
cd mongolite
|
|
79
|
+
npm install
|
|
80
|
+
npm test # Run tests
|
|
81
|
+
npm run build # Compile TypeScript
|
|
82
|
+
npm run lint # Lint code
|
|
645
83
|
```
|
|
646
84
|
|
|
647
|
-
These tests help prevent common module resolution issues that can occur in ESM packages.
|
|
648
|
-
|
|
649
85
|
## Contributing
|
|
650
86
|
|
|
651
|
-
Contributions are welcome! Please
|
|
87
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting a pull request.
|
|
652
88
|
|
|
653
89
|
## License
|
|
654
90
|
|
|
655
|
-
MIT
|
|
91
|
+
[MIT](./LICENSE)
|