mongolite-ts 0.6.2 → 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 CHANGED
@@ -3,653 +3,89 @@
3
3
  [![CI](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml/badge.svg)](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml)
4
4
  [![NPM version](https://img.shields.io/npm/v/mongolite-ts.svg)](https://www.npmjs.com/package/mongolite-ts)
5
5
  [![Codecov](https://codecov.io/gh/semics-tech/mongolite/branch/master/graph/badge.svg)](https://codecov.io/gh/semics-tech/mongolite)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
7
 
7
- A MongoDB-like client that uses SQLite as its underlying persistent store. Written in TypeScript, this package provides a familiar API for developers accustomed to MongoDB, while leveraging the simplicity and file-based nature of SQLite. It supports basic CRUD operations and JSON querying capabilities.
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
- ## Features
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
- ### Example Usage
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
- ```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
- }
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
- For detailed information about JSON safety features, see [JSON_SAFETY.md](./docs/JSON_SAFETY.md).
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
- ## Usage
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
- // Initialize the client.
91
- // You can use ':memory:' for an in-memory database, or provide a file path.
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
- const foundUserByEmail = await usersCollection.findOne({ email: 'alice@example.com' });
119
- console.log('Found user by email:', foundUserByEmail);
44
+ const users = client.collection('users');
120
45
 
121
- // Find with nested query
122
- const foundUserByCity = await usersCollection.findOne({ 'address.city': 'Anytown' });
123
- console.log('Found user by city:', foundUserByCity);
46
+ // Insert
47
+ const result = await users.insertOne({ name: 'Alice', age: 30 });
124
48
 
125
- // Find with operator ($gt)
126
- const olderUsers = await usersCollection.find({ age: { $gt: 25 } }).toArray();
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
- // 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);
55
+ // Delete
56
+ await users.deleteOne({ name: 'Alice' });
136
57
 
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
- }
58
+ await client.close();
154
59
  }
155
60
 
156
61
  main();
157
62
  ```
158
63
 
159
- ## Change Streams
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
- # Build and verify exports
638
- npm run verify-build
639
-
640
- # Test the package as a third-party dependency
641
- npm run test-third-party
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 open an issue or submit a pull request.
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)