mongolite-ts 0.1.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.
@@ -0,0 +1,643 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongoLiteCollection = exports.FindCursor = void 0;
4
+ const uuid_1 = require("uuid");
5
+ /**
6
+ * Represents a cursor for find operations, allowing chaining of limit, skip, and sort.
7
+ */
8
+ class FindCursor {
9
+ constructor(db, collectionName, initialFilter) {
10
+ this.db = db;
11
+ this.collectionName = collectionName;
12
+ this.limitCount = null;
13
+ this.skipCount = null;
14
+ this.sortCriteria = null;
15
+ this.projectionFields = null;
16
+ this.queryParts = this.buildSelectQuery(initialFilter);
17
+ }
18
+ parseJsonPath(path) {
19
+ return `'$.${path.replace(/\./g, '.')}'`;
20
+ }
21
+ buildWhereClause(filter, params) {
22
+ const conditions = [];
23
+ // Handle $and, $or, $nor logical operators at the top level
24
+ if (filter.$and) {
25
+ const andConditions = filter.$and
26
+ .map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
27
+ .join(' AND ');
28
+ conditions.push(`(${andConditions})`);
29
+ }
30
+ else if (filter.$or) {
31
+ const orConditions = filter.$or
32
+ .map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
33
+ .join(' OR ');
34
+ conditions.push(`(${orConditions})`);
35
+ }
36
+ else if (filter.$nor) {
37
+ const norConditions = filter.$nor
38
+ .map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
39
+ .join(' OR ');
40
+ conditions.push(`NOT (${norConditions})`);
41
+ }
42
+ else {
43
+ // Handle field conditions
44
+ for (const key in filter) {
45
+ if (key.startsWith('$'))
46
+ continue; // Skip logical operators already handled
47
+ const value = filter[key];
48
+ if (key === '_id') {
49
+ if (typeof value === 'string') {
50
+ conditions.push('_id = ?');
51
+ params.push(value);
52
+ }
53
+ else if (typeof value === 'object' &&
54
+ value !== null &&
55
+ value.$in) {
56
+ const inValues = value.$in;
57
+ if (inValues.length > 0) {
58
+ conditions.push(`_id IN (${inValues.map(() => '?').join(',')})`);
59
+ params.push(...inValues);
60
+ }
61
+ else {
62
+ conditions.push('1=0'); // No values in $in means nothing matches
63
+ }
64
+ }
65
+ else if (typeof value === 'object' &&
66
+ value !== null &&
67
+ value.$ne) {
68
+ conditions.push('_id <> ?');
69
+ params.push(value.$ne);
70
+ }
71
+ // Add other _id specific operators if needed
72
+ }
73
+ else {
74
+ const jsonPath = this.parseJsonPath(key);
75
+ if (typeof value === 'object' && value !== null) {
76
+ // Handle operators like $gt, $lt, $in, $exists, $not etc.
77
+ for (const op in value) {
78
+ const opValue = value[op];
79
+ switch (op) {
80
+ case '$eq':
81
+ conditions.push(`json_extract(data, ${jsonPath}) = json(?)`);
82
+ params.push(JSON.stringify(opValue));
83
+ break;
84
+ case '$ne':
85
+ conditions.push(`json_extract(data, ${jsonPath}) != json(?)`);
86
+ params.push(JSON.stringify(opValue));
87
+ break;
88
+ case '$gt':
89
+ conditions.push(`json_extract(data, ${jsonPath}) > json(?)`);
90
+ params.push(JSON.stringify(opValue));
91
+ break;
92
+ case '$gte':
93
+ conditions.push(`json_extract(data, ${jsonPath}) >= json(?)`);
94
+ params.push(JSON.stringify(opValue));
95
+ break;
96
+ case '$lt':
97
+ conditions.push(`json_extract(data, ${jsonPath}) < json(?)`);
98
+ params.push(JSON.stringify(opValue));
99
+ break;
100
+ case '$lte':
101
+ conditions.push(`json_extract(data, ${jsonPath}) <= json(?)`);
102
+ params.push(JSON.stringify(opValue));
103
+ break;
104
+ case '$in':
105
+ if (Array.isArray(opValue) && opValue.length > 0) {
106
+ const inConditions = opValue
107
+ .map(() => `json_extract(data, ${jsonPath}) = json(?)`)
108
+ .join(' OR ');
109
+ conditions.push(`(${inConditions})`);
110
+ opValue.forEach((val) => params.push(JSON.stringify(val)));
111
+ }
112
+ else {
113
+ conditions.push('1=0'); // Empty $in array, nothing will match
114
+ }
115
+ break;
116
+ case '$nin':
117
+ if (Array.isArray(opValue) && opValue.length > 0) {
118
+ const ninConditions = opValue
119
+ .map(() => `json_extract(data, ${jsonPath}) != json(?)`)
120
+ .join(' AND ');
121
+ conditions.push(`(${ninConditions})`);
122
+ opValue.forEach((val) => params.push(JSON.stringify(val)));
123
+ }
124
+ // Empty $nin array means match everything, so no condition needed
125
+ break;
126
+ case '$exists':
127
+ if (opValue === true) {
128
+ conditions.push(`json_extract(data, ${jsonPath}) IS NOT NULL`);
129
+ }
130
+ else {
131
+ conditions.push(`json_extract(data, ${jsonPath}) IS NULL`);
132
+ }
133
+ break;
134
+ // Add other operators as needed
135
+ }
136
+ }
137
+ }
138
+ else {
139
+ // Direct equality for non-object values
140
+ conditions.push(`json_extract(data, ${jsonPath}) = json(?)`);
141
+ params.push(JSON.stringify(value));
142
+ }
143
+ }
144
+ }
145
+ }
146
+ return conditions.length > 0 ? conditions.join(' AND ') : '1=1';
147
+ }
148
+ buildSelectQuery(filter) {
149
+ const params = [];
150
+ const whereClause = this.buildWhereClause(filter, params);
151
+ const sql = `SELECT _id, data FROM "${this.collectionName}" WHERE ${whereClause}`;
152
+ return { sql, params };
153
+ }
154
+ /**
155
+ * Specifies the maximum number of documents the cursor will return.
156
+ * @param count The number of documents to limit to.
157
+ * @returns The `FindCursor` instance for chaining.
158
+ */
159
+ limit(count) {
160
+ if (count < 0)
161
+ throw new Error('Limit must be a non-negative number.');
162
+ this.limitCount = count;
163
+ return this;
164
+ }
165
+ /**
166
+ * Specifies the number of documents to skip.
167
+ * @param count The number of documents to skip.
168
+ * @returns The `FindCursor` instance for chaining.
169
+ */
170
+ skip(count) {
171
+ if (count < 0)
172
+ throw new Error('Skip must be a non-negative number.');
173
+ this.skipCount = count;
174
+ return this;
175
+ }
176
+ /**
177
+ * Specifies the sorting order for the documents.
178
+ * @param sortCriteria An object defining sort order (e.g., `{ age: -1, name: 1 }`).
179
+ * @returns The `FindCursor` instance for chaining.
180
+ */
181
+ sort(sortCriteria) {
182
+ this.sortCriteria = sortCriteria;
183
+ return this;
184
+ }
185
+ /**
186
+ * Specifies the fields to return (projection).
187
+ * @param projection An object where keys are field names and values are 1 (include) or 0 (exclude).
188
+ * `_id` is included by default unless explicitly excluded.
189
+ * @returns The `FindCursor` instance for chaining.
190
+ */
191
+ project(projection) {
192
+ this.projectionFields = projection;
193
+ return this;
194
+ }
195
+ applyProjection(doc) {
196
+ if (!this.projectionFields)
197
+ return doc;
198
+ const projectedDoc = {};
199
+ let includeMode = true; // true if any field is 1, false if any field is 0 (excluding _id)
200
+ let hasExplicitInclusion = false;
201
+ // Determine if it's an inclusion or exclusion projection
202
+ for (const key in this.projectionFields) {
203
+ if (key === '_id')
204
+ continue;
205
+ if (this.projectionFields[key] === 1) {
206
+ hasExplicitInclusion = true;
207
+ break;
208
+ }
209
+ if (this.projectionFields[key] === 0) {
210
+ includeMode = false;
211
+ // No break here, need to check all for explicit inclusions if _id is also 0
212
+ }
213
+ }
214
+ if (this.projectionFields._id === 0 && !hasExplicitInclusion) {
215
+ // If _id is excluded and no other fields are explicitly included,
216
+ // it's an exclusion projection where other fields are implicitly included.
217
+ includeMode = false;
218
+ }
219
+ else if (hasExplicitInclusion) {
220
+ includeMode = true;
221
+ }
222
+ if (includeMode) {
223
+ // Inclusion mode
224
+ for (const key in this.projectionFields) {
225
+ if (this.projectionFields[key] === 1) {
226
+ if (key.includes('.')) {
227
+ // Handle nested paths for inclusion (basic implementation)
228
+ const path = key.split('.');
229
+ let current = doc;
230
+ let target = projectedDoc;
231
+ // Navigate to the last parent in the path
232
+ for (let i = 0; i < path.length - 1; i++) {
233
+ const segment = path[i];
234
+ if (current[segment] === undefined)
235
+ break;
236
+ if (target[segment] === undefined) {
237
+ target[segment] = {};
238
+ }
239
+ current = current[segment];
240
+ target = target[segment];
241
+ }
242
+ // Set the final property if we reached it
243
+ const lastSegment = path[path.length - 1];
244
+ if (current && current[lastSegment] !== undefined) {
245
+ target[lastSegment] = current[lastSegment];
246
+ }
247
+ }
248
+ else if (key in doc) {
249
+ projectedDoc[key] = doc[key];
250
+ }
251
+ }
252
+ }
253
+ // _id is included by default in inclusion mode, unless explicitly excluded
254
+ if (this.projectionFields._id !== 0 && '_id' in doc) {
255
+ projectedDoc._id = doc._id;
256
+ }
257
+ }
258
+ else {
259
+ // Exclusion mode
260
+ Object.assign(projectedDoc, doc);
261
+ for (const key in this.projectionFields) {
262
+ if (this.projectionFields[key] === 0) {
263
+ if (key.includes('.')) {
264
+ // Handle nested paths for exclusion (basic implementation)
265
+ const path = key.split('.');
266
+ let current = projectedDoc;
267
+ // Navigate to the parent of the property to exclude
268
+ for (let i = 0; i < path.length - 1; i++) {
269
+ const segment = path[i];
270
+ if (current[segment] === undefined)
271
+ break;
272
+ current = current[segment];
273
+ }
274
+ // Delete the final property if we reached its parent
275
+ const lastSegment = path[path.length - 1];
276
+ if (current && current[lastSegment] !== undefined) {
277
+ delete current[lastSegment];
278
+ }
279
+ }
280
+ else {
281
+ delete projectedDoc[key];
282
+ }
283
+ }
284
+ }
285
+ }
286
+ return projectedDoc;
287
+ }
288
+ /**
289
+ * Executes the query and returns all matching documents as an array.
290
+ * @returns A promise that resolves to an array of documents.
291
+ */
292
+ async toArray() {
293
+ let finalSql = this.queryParts.sql;
294
+ const finalParams = [...this.queryParts.params];
295
+ if (this.sortCriteria) {
296
+ const sortClauses = Object.entries(this.sortCriteria).map(([field, order]) => {
297
+ if (field === '_id') {
298
+ return `_id ${order === 1 ? 'ASC' : 'DESC'}`;
299
+ }
300
+ return `json_extract(data, ${this.parseJsonPath(field)}) ${order === 1 ? 'ASC' : 'DESC'}`;
301
+ });
302
+ if (sortClauses.length > 0) {
303
+ finalSql += ` ORDER BY ${sortClauses.join(', ')}`;
304
+ }
305
+ }
306
+ if (this.limitCount !== null) {
307
+ finalSql += ` LIMIT ?`;
308
+ finalParams.push(this.limitCount);
309
+ }
310
+ if (this.skipCount !== null) {
311
+ if (this.limitCount === null) {
312
+ // SQLite requires a LIMIT if OFFSET is used.
313
+ // Use a very large number if no limit is specified.
314
+ finalSql += ` LIMIT -1`; // Or a large number like 999999999
315
+ }
316
+ finalSql += ` OFFSET ?`;
317
+ finalParams.push(this.skipCount);
318
+ }
319
+ const rows = await this.db.all(finalSql, finalParams);
320
+ return rows.map((row) => {
321
+ const doc = { _id: row._id, ...JSON.parse(row.data) };
322
+ return this.applyProjection(doc);
323
+ });
324
+ }
325
+ }
326
+ exports.FindCursor = FindCursor;
327
+ /**
328
+ * MongoLiteCollection provides methods to interact with a specific SQLite table
329
+ * as if it were a MongoDB collection.
330
+ */
331
+ class MongoLiteCollection {
332
+ constructor(db, name) {
333
+ this.db = db;
334
+ this.name = name;
335
+ this.ensureTable().catch((err) => {
336
+ // This error should be handled or logged appropriately.
337
+ // For now, console.error is used. In a real app, a more robust
338
+ // error handling mechanism would be needed, potentially failing
339
+ // the collection initialization or notifying the user.
340
+ console.error(`Failed to ensure table ${this.name} exists:`, err);
341
+ });
342
+ }
343
+ /**
344
+ * Ensures the SQLite table for this collection exists, creating it if necessary.
345
+ * The table will have an `_id` column (indexed) and a `data` column for JSON.
346
+ * @private
347
+ */
348
+ async ensureTable() {
349
+ // Using " " around table name to handle names with special characters or keywords
350
+ const createTableSQL = `
351
+ CREATE TABLE IF NOT EXISTS "${this.name}" (
352
+ _id TEXT PRIMARY KEY,
353
+ data TEXT
354
+ );
355
+ `;
356
+ // It's generally good practice to create an index on _id, but TEXT PRIMARY KEY often implies an index.
357
+ // Explicitly creating an index can be done if performance dictates.
358
+ // const createIndexSQL = `CREATE UNIQUE INDEX IF NOT EXISTS idx_${this.name}__id ON "${this.name}"(_id);`;
359
+ try {
360
+ await this.db.exec(createTableSQL);
361
+ // await this.db.exec(createIndexSQL); // If explicit index is desired
362
+ }
363
+ catch (error) {
364
+ console.error(`Error ensuring table "${this.name}":`, error);
365
+ throw error; // Re-throw to allow caller to handle
366
+ }
367
+ }
368
+ /**
369
+ * Inserts a single document into the collection.
370
+ * If `_id` is not provided, a UUID will be generated.
371
+ * @param doc The document to insert.
372
+ * @returns {Promise<InsertOneResult>} An object containing the outcome of the insert operation.
373
+ */
374
+ async insertOne(doc) {
375
+ await this.ensureTable(); // Ensure table exists before insert
376
+ const docId = doc._id || (0, uuid_1.v4)();
377
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
378
+ const { _id, ...dataToStore } = { ...doc, _id: docId }; // Ensure _id is part of the internal structure
379
+ const jsonData = JSON.stringify(dataToStore);
380
+ const sql = `INSERT INTO "${this.name}" (_id, data) VALUES (?, ?)`;
381
+ try {
382
+ await this.db.run(sql, [docId, jsonData]);
383
+ return { acknowledged: true, insertedId: docId };
384
+ }
385
+ catch (error) {
386
+ // Handle potential errors, e.g., unique constraint violation if _id already exists
387
+ console.error(`Error inserting document into ${this.name}:`, error);
388
+ // Check for unique constraint error (SQLite specific error code)
389
+ if (error.code === 'SQLITE_CONSTRAINT') {
390
+ throw new Error(`Duplicate _id: ${docId}`);
391
+ }
392
+ throw error; // Re-throw other errors
393
+ }
394
+ }
395
+ /**
396
+ * Finds a single document matching the filter.
397
+ * @param filter The query criteria.
398
+ * @param projection Optional. Specifies the fields to return.
399
+ * @returns {Promise<T | null>} The found document or `null`.
400
+ */
401
+ async findOne(filter, projection) {
402
+ await this.ensureTable();
403
+ const cursor = this.find(filter).limit(1);
404
+ if (projection) {
405
+ cursor.project(projection);
406
+ }
407
+ const results = await cursor.toArray();
408
+ return results.length > 0 ? results[0] : null;
409
+ }
410
+ /**
411
+ * Finds multiple documents matching the filter and returns a cursor.
412
+ * @param filter The query criteria.
413
+ * @returns {FindCursor<T>} A `FindCursor` instance.
414
+ */
415
+ find(filter = {}) {
416
+ // ensureTable is called by operations on the cursor or by constructor
417
+ return new FindCursor(this.db, this.name, filter);
418
+ }
419
+ /**
420
+ * Updates a single document matching the filter.
421
+ * @param filter The selection criteria for the update.
422
+ * @param update The modifications to apply.
423
+ * @returns {Promise<UpdateResult>} An object describing the outcome.
424
+ */
425
+ async updateOne(filter, update) {
426
+ await this.ensureTable();
427
+ // Find the document first (only one)
428
+ const paramsForSelect = [];
429
+ const whereClause = new FindCursor(this.db, this.name, filter)['buildWhereClause'](filter, paramsForSelect);
430
+ const selectSql = `SELECT _id, data FROM "${this.name}" WHERE ${whereClause} LIMIT 1`;
431
+ const rowToUpdate = await this.db.get(selectSql, paramsForSelect);
432
+ if (!rowToUpdate) {
433
+ return { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedId: null };
434
+ }
435
+ let currentDoc = JSON.parse(rowToUpdate.data);
436
+ let modified = false;
437
+ // Process update operators
438
+ for (const operator in update) {
439
+ const opArgs = update[operator];
440
+ if (!opArgs)
441
+ continue;
442
+ switch (operator) {
443
+ case '$set':
444
+ for (const path in opArgs) {
445
+ const value = opArgs[path];
446
+ this.setNestedValue(currentDoc, path, value);
447
+ modified = true;
448
+ }
449
+ break;
450
+ case '$unset':
451
+ for (const path in opArgs) {
452
+ this.unsetNestedValue(currentDoc, path);
453
+ modified = true;
454
+ }
455
+ break;
456
+ case '$inc':
457
+ for (const path in opArgs) {
458
+ const value = opArgs[path];
459
+ if (typeof value === 'number') {
460
+ const currentValue = this.getNestedValue(currentDoc, path) || 0;
461
+ if (typeof currentValue === 'number') {
462
+ this.setNestedValue(currentDoc, path, currentValue + value);
463
+ modified = true;
464
+ }
465
+ }
466
+ }
467
+ break;
468
+ case '$push':
469
+ for (const path in opArgs) {
470
+ const value = opArgs[path];
471
+ const currentValue = this.getNestedValue(currentDoc, path);
472
+ if (Array.isArray(currentValue)) {
473
+ if (typeof value === 'object' && value !== null && '$each' in value) {
474
+ if (Array.isArray(value.$each)) {
475
+ currentValue.push(...value.$each);
476
+ modified = true;
477
+ }
478
+ }
479
+ else {
480
+ currentValue.push(value);
481
+ modified = true;
482
+ }
483
+ }
484
+ else if (currentValue === undefined) {
485
+ // If the field doesn't exist, create it as an array
486
+ if (typeof value === 'object' && value !== null && '$each' in value) {
487
+ if (Array.isArray(value.$each)) {
488
+ this.setNestedValue(currentDoc, path, [...value.$each]);
489
+ modified = true;
490
+ }
491
+ }
492
+ else {
493
+ this.setNestedValue(currentDoc, path, [value]);
494
+ modified = true;
495
+ }
496
+ }
497
+ }
498
+ break;
499
+ case '$pull':
500
+ for (const path in opArgs) {
501
+ const value = opArgs[path];
502
+ const currentValue = this.getNestedValue(currentDoc, path);
503
+ if (Array.isArray(currentValue)) {
504
+ // Simple equality pull
505
+ const newArray = currentValue.filter((item) => {
506
+ if (typeof item === 'object' && typeof value === 'object') {
507
+ // For objects, do a deep comparison (simplified)
508
+ return JSON.stringify(item) !== JSON.stringify(value);
509
+ }
510
+ return item !== value;
511
+ });
512
+ if (newArray.length !== currentValue.length) {
513
+ this.setNestedValue(currentDoc, path, newArray);
514
+ modified = true;
515
+ }
516
+ }
517
+ }
518
+ break;
519
+ }
520
+ }
521
+ if (modified) {
522
+ // Get the modifiedCount
523
+ const matchedRecordSql = `SELECT COUNT(*) as count FROM "${this.name}" WHERE ${whereClause}`;
524
+ const matchedRecord = await this.db.get(matchedRecordSql, paramsForSelect);
525
+ const matchedCount = matchedRecord?.count || 0;
526
+ // Update the document in SQLite
527
+ const updateSql = `UPDATE "${this.name}" SET data = ? WHERE _id = ?`;
528
+ const updateParams = [JSON.stringify(currentDoc), rowToUpdate._id];
529
+ await this.db.run(updateSql, updateParams);
530
+ return {
531
+ acknowledged: true,
532
+ matchedCount: 1,
533
+ modifiedCount: matchedCount,
534
+ upsertedId: null, // We don't support upsert yet
535
+ };
536
+ }
537
+ return {
538
+ acknowledged: true,
539
+ matchedCount: 1,
540
+ modifiedCount: 0, // No changes were made
541
+ upsertedId: null,
542
+ };
543
+ }
544
+ // Helper for $set, $inc to handle dot notation
545
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
546
+ setNestedValue(obj, path, value) {
547
+ const keys = path.split('.');
548
+ let current = obj;
549
+ // Navigate to the last parent in the path
550
+ for (let i = 0; i < keys.length - 1; i++) {
551
+ const key = keys[i];
552
+ // Create nested objects if they don't exist
553
+ if (current[key] === undefined || current[key] === null) {
554
+ current[key] = {};
555
+ }
556
+ else if (typeof current[key] !== 'object') {
557
+ // If it's not an object but we need to go deeper, replace it with an object
558
+ current[key] = {};
559
+ }
560
+ current = current[key];
561
+ }
562
+ // Set the value at the final key
563
+ current[keys[keys.length - 1]] = value;
564
+ }
565
+ // Helper for $unset
566
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
567
+ unsetNestedValue(obj, path) {
568
+ const keys = path.split('.');
569
+ let current = obj;
570
+ // Navigate to the last parent in the path
571
+ for (let i = 0; i < keys.length - 1; i++) {
572
+ const key = keys[i];
573
+ if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
574
+ return; // Path doesn't exist, nothing to unset
575
+ }
576
+ current = current[key];
577
+ }
578
+ // Delete the property at the final key
579
+ delete current[keys[keys.length - 1]];
580
+ }
581
+ // Helper to get nested value
582
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
583
+ getNestedValue(obj, path) {
584
+ const keys = path.split('.');
585
+ let current = obj;
586
+ // Navigate through the path
587
+ for (let i = 0; i < keys.length; i++) {
588
+ const key = keys[i];
589
+ if (current[key] === undefined) {
590
+ return undefined; // Path doesn't exist
591
+ }
592
+ current = current[key];
593
+ }
594
+ return current;
595
+ }
596
+ /**
597
+ * Deletes a single document matching the filter.
598
+ * @param filter The criteria to select the document to delete.
599
+ * @returns {Promise<DeleteResult>} An object describing the outcome.
600
+ */
601
+ async deleteOne(filter) {
602
+ await this.ensureTable();
603
+ const paramsForDelete = [];
604
+ const whereClause = new FindCursor(this.db, this.name, filter)['buildWhereClause'](filter, paramsForDelete);
605
+ // Get the number of documents that would be deleted
606
+ const countSql = `SELECT COUNT(*) as count FROM "${this.name}" WHERE ${whereClause}`;
607
+ const countResult = await this.db.get(countSql, paramsForDelete);
608
+ const deleteSql = `
609
+ DELETE FROM "${this.name}"
610
+ WHERE ROWID IN (SELECT ROWID FROM "${this.name}" WHERE ${whereClause} LIMIT 1);
611
+ `;
612
+ await this.db.run(deleteSql, paramsForDelete);
613
+ return { acknowledged: true, deletedCount: countResult ? 1 : 0 };
614
+ }
615
+ /**
616
+ * Deletes multiple documents matching the filter.
617
+ * @param filter The criteria to select documents to delete.
618
+ * @returns {Promise<DeleteResult>} An object describing the outcome.
619
+ */
620
+ async deleteMany(filter) {
621
+ await this.ensureTable();
622
+ const paramsForDelete = [];
623
+ const whereClause = new FindCursor(this.db, this.name, filter)['buildWhereClause'](filter, paramsForDelete);
624
+ const deleteSql = `DELETE FROM "${this.name}" WHERE ${whereClause}`;
625
+ const result = await this.db.run(deleteSql, paramsForDelete);
626
+ return { acknowledged: true, deletedCount: result.changes || 0 };
627
+ }
628
+ /**
629
+ * Counts the number of documents matching the filter.
630
+ * @param filter The criteria to select documents to count.
631
+ * @returns {Promise<number>} The count of matching documents.
632
+ */
633
+ async countDocuments(filter = {}) {
634
+ await this.ensureTable();
635
+ const paramsForCount = [];
636
+ const whereClause = new FindCursor(this.db, this.name, filter)['buildWhereClause'](filter, paramsForCount);
637
+ const countSql = `SELECT COUNT(*) as count FROM "${this.name}" WHERE ${whereClause}`;
638
+ const result = await this.db.get(countSql, paramsForCount);
639
+ return result?.count || 0;
640
+ }
641
+ }
642
+ exports.MongoLiteCollection = MongoLiteCollection;
643
+ //# sourceMappingURL=collection.js.map