mongolite-ts 0.1.19 → 0.1.22
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 +10 -0
- package/dist/collection.d.ts +2 -51
- package/dist/collection.js +1 -544
- package/dist/collection.js.map +1 -1
- package/dist/cursors/findCursor.d.ts +64 -0
- package/dist/cursors/findCursor.js +449 -0
- package/dist/cursors/findCursor.js.map +1 -0
- package/dist/document-utils.d.ts +34 -0
- package/dist/document-utils.js +101 -0
- package/dist/document-utils.js.map +1 -0
- package/dist/find-cursor.d.ts +51 -0
- package/dist/find-cursor.js +204 -0
- package/dist/find-cursor.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/query-builder.d.ts +18 -0
- package/dist/query-builder.js +358 -0
- package/dist/query-builder.js.map +1 -0
- package/dist/types.d.ts +9 -2
- package/dist/update-operations.d.ts +17 -0
- package/dist/update-operations.js +147 -0
- package/dist/update-operations.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -134,6 +134,16 @@ Explicitly opens the database connection. Operations will automatically connect
|
|
|
134
134
|
|
|
135
135
|
Closes the database connection.
|
|
136
136
|
|
|
137
|
+
#### `listCollections(): Promise<string[]>`
|
|
138
|
+
|
|
139
|
+
Lists all collections (tables) in the database.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Get all collections
|
|
143
|
+
const collections = await client.listCollections().toArray();
|
|
144
|
+
console.log('Available collections:', collections);
|
|
145
|
+
```
|
|
146
|
+
|
|
137
147
|
#### `collection<T extends DocumentWithId = DocumentWithId>(name: string): MongoLiteCollection<T>`
|
|
138
148
|
|
|
139
149
|
Gets a reference to a collection (table).
|
package/dist/collection.d.ts
CHANGED
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
import { SQLiteDB } from './db.js';
|
|
2
|
-
import { DocumentWithId, Filter, UpdateFilter, InsertOneResult, UpdateResult, DeleteResult,
|
|
3
|
-
|
|
4
|
-
* Represents a cursor for find operations, allowing chaining of limit, skip, and sort.
|
|
5
|
-
*/
|
|
6
|
-
export declare class FindCursor<T extends DocumentWithId> {
|
|
7
|
-
private db;
|
|
8
|
-
private collectionName;
|
|
9
|
-
private readonly options;
|
|
10
|
-
private queryParts;
|
|
11
|
-
private limitCount;
|
|
12
|
-
private skipCount;
|
|
13
|
-
private sortCriteria;
|
|
14
|
-
private projectionFields;
|
|
15
|
-
constructor(db: SQLiteDB, collectionName: string, initialFilter: Filter<T>, options?: {
|
|
16
|
-
verbose?: boolean;
|
|
17
|
-
});
|
|
18
|
-
private parseJsonPath;
|
|
19
|
-
private buildWhereClause;
|
|
20
|
-
private buildSelectQuery;
|
|
21
|
-
/**
|
|
22
|
-
* Specifies the maximum number of documents the cursor will return.
|
|
23
|
-
* @param count The number of documents to limit to.
|
|
24
|
-
* @returns The `FindCursor` instance for chaining.
|
|
25
|
-
*/
|
|
26
|
-
limit(count: number): this;
|
|
27
|
-
/**
|
|
28
|
-
* Specifies the number of documents to skip.
|
|
29
|
-
* @param count The number of documents to skip.
|
|
30
|
-
* @returns The `FindCursor` instance for chaining.
|
|
31
|
-
*/
|
|
32
|
-
skip(count: number): this;
|
|
33
|
-
/**
|
|
34
|
-
* Specifies the sorting order for the documents.
|
|
35
|
-
* @param sortCriteria An object defining sort order (e.g., `{ age: -1, name: 1 }`).
|
|
36
|
-
* @returns The `FindCursor` instance for chaining.
|
|
37
|
-
*/
|
|
38
|
-
sort(sortCriteria: SortCriteria<T>): this;
|
|
39
|
-
/**
|
|
40
|
-
* Specifies the fields to return (projection).
|
|
41
|
-
* @param projection An object where keys are field names and values are 1 (include) or 0 (exclude).
|
|
42
|
-
* `_id` is included by default unless explicitly excluded.
|
|
43
|
-
* @returns The `FindCursor` instance for chaining.
|
|
44
|
-
*/
|
|
45
|
-
project(projection: Projection<T>): this;
|
|
46
|
-
private applyProjection;
|
|
47
|
-
/**
|
|
48
|
-
* Executes the query and returns all matching documents as an array.
|
|
49
|
-
* @returns A promise that resolves to an array of documents.
|
|
50
|
-
*/
|
|
51
|
-
toArray(): Promise<Partial<T>[]>;
|
|
52
|
-
}
|
|
2
|
+
import { DocumentWithId, Filter, UpdateFilter, InsertOneResult, UpdateResult, DeleteResult, Projection } from './types.js';
|
|
3
|
+
import { FindCursor } from './cursors/findCursor.js';
|
|
53
4
|
/**
|
|
54
5
|
* MongoLiteCollection provides methods to interact with a specific SQLite table
|
|
55
6
|
* as if it were a MongoDB collection.
|
package/dist/collection.js
CHANGED
|
@@ -1,548 +1,5 @@
|
|
|
1
1
|
import { ObjectId } from 'bson';
|
|
2
|
-
|
|
3
|
-
* Represents a cursor for find operations, allowing chaining of limit, skip, and sort.
|
|
4
|
-
*/
|
|
5
|
-
export class FindCursor {
|
|
6
|
-
constructor(db, collectionName, initialFilter, options = {}) {
|
|
7
|
-
this.db = db;
|
|
8
|
-
this.collectionName = collectionName;
|
|
9
|
-
this.options = options;
|
|
10
|
-
this.limitCount = null;
|
|
11
|
-
this.skipCount = null;
|
|
12
|
-
this.sortCriteria = null;
|
|
13
|
-
this.projectionFields = null;
|
|
14
|
-
this.queryParts = this.buildSelectQuery(initialFilter);
|
|
15
|
-
}
|
|
16
|
-
parseJsonPath(path) {
|
|
17
|
-
return `'$.${path.replace(/\./g, '.')}'`;
|
|
18
|
-
}
|
|
19
|
-
buildWhereClause(filter, params) {
|
|
20
|
-
const conditions = [];
|
|
21
|
-
// Handle $and, $or, $nor logical operators at the top level
|
|
22
|
-
for (const key of Object.keys(filter)) {
|
|
23
|
-
if (key === '$and' && filter.$and) {
|
|
24
|
-
const condition = filter.$and;
|
|
25
|
-
const andConditions = condition
|
|
26
|
-
.map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
|
|
27
|
-
.join(' AND ');
|
|
28
|
-
conditions.push(`(${andConditions})`);
|
|
29
|
-
}
|
|
30
|
-
else if (key === '$or' && filter.$or) {
|
|
31
|
-
const condition = filter.$or;
|
|
32
|
-
const orConditions = condition
|
|
33
|
-
.map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
|
|
34
|
-
.join(' OR ');
|
|
35
|
-
conditions.push(`(${orConditions})`);
|
|
36
|
-
}
|
|
37
|
-
else if (key === '$nor' && filter.$nor) {
|
|
38
|
-
const condition = filter.$nor;
|
|
39
|
-
const norConditions = condition
|
|
40
|
-
.map((subFilter) => `(${this.buildWhereClause(subFilter, params)})`)
|
|
41
|
-
.join(' OR ');
|
|
42
|
-
conditions.push(`NOT (${norConditions})`);
|
|
43
|
-
}
|
|
44
|
-
else if (key === '$text' && filter.$text) {
|
|
45
|
-
// Handle text search (basic implementation)
|
|
46
|
-
const search = filter.$text.$search;
|
|
47
|
-
if (typeof search === 'string' && search.trim() !== '') {
|
|
48
|
-
conditions.push(`data LIKE ?`);
|
|
49
|
-
params.push(`%${search}%`); // Simple LIKE search
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
conditions.push('1=0'); // No valid search term, nothing matches
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (key === '$exists' && filter.$exists) {
|
|
56
|
-
// Handle existence check
|
|
57
|
-
const existsConditions = Object.entries(filter.$exists)
|
|
58
|
-
.map(([field, exists]) => {
|
|
59
|
-
const jsonPath = this.parseJsonPath(field);
|
|
60
|
-
if (exists) {
|
|
61
|
-
return `json_extract(data, ${jsonPath}) IS NOT NULL`;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
return `json_extract(data, ${jsonPath}) IS NULL`;
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.join(' AND ');
|
|
68
|
-
conditions.push(`(${existsConditions})`);
|
|
69
|
-
}
|
|
70
|
-
else if (key === '$not' && filter.$not) {
|
|
71
|
-
// Handle negation of conditions
|
|
72
|
-
const notConditions = filter.$not;
|
|
73
|
-
const notClause = this.buildWhereClause(notConditions, params);
|
|
74
|
-
conditions.push(`NOT (${notClause})`);
|
|
75
|
-
}
|
|
76
|
-
else if (key === '$all' && filter.$all) {
|
|
77
|
-
// Handle array all match
|
|
78
|
-
const allConditions = Object.entries(filter.$all)
|
|
79
|
-
.map(([field, values]) => {
|
|
80
|
-
const arrayPath = this.parseJsonPath(field);
|
|
81
|
-
if (Array.isArray(values) && values.length > 0) {
|
|
82
|
-
const inConditions = values
|
|
83
|
-
.map(() => `json_extract(data, ${arrayPath}) = ?`)
|
|
84
|
-
.join(' AND ');
|
|
85
|
-
params.push(...values);
|
|
86
|
-
return `(${inConditions})`;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
return '1=0'; // Empty $all means nothing matches
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
.join(' AND ');
|
|
93
|
-
conditions.push(`(${allConditions})`);
|
|
94
|
-
}
|
|
95
|
-
else if (key === '$elemMatch' && filter.$elemMatch) {
|
|
96
|
-
// Handle array element match
|
|
97
|
-
const elemMatchConditions = Object.entries(filter.$elemMatch)
|
|
98
|
-
.map(([field, subFilter]) => {
|
|
99
|
-
const arrayPath = this.parseJsonPath(field);
|
|
100
|
-
// First check if the field exists and is an array
|
|
101
|
-
const checkArrayCondition = `json_type(json_extract(data, ${arrayPath})) = 'array'`;
|
|
102
|
-
// Use a subquery with json_each to find at least one array element matching all conditions
|
|
103
|
-
let elemMatchSubquery = `EXISTS (
|
|
104
|
-
SELECT 1 FROM json_each(json_extract(data, ${arrayPath})) as elements
|
|
105
|
-
WHERE `;
|
|
106
|
-
// Process the conditions for the array elements
|
|
107
|
-
if (typeof subFilter === 'object' && subFilter !== null) {
|
|
108
|
-
const elemConditions = Object.entries(subFilter)
|
|
109
|
-
.map(([subField, subValue]) => {
|
|
110
|
-
if (typeof subValue === 'object' && subValue !== null) {
|
|
111
|
-
// Handle operators in the subfilter
|
|
112
|
-
const subConditions = [];
|
|
113
|
-
for (const op in subValue) {
|
|
114
|
-
const opValue = subValue[op];
|
|
115
|
-
switch (op) {
|
|
116
|
-
case '$eq':
|
|
117
|
-
subConditions.push(`json_extract(elements.value, '$.${subField}') = ?`);
|
|
118
|
-
params.push(opValue);
|
|
119
|
-
break;
|
|
120
|
-
case '$gt':
|
|
121
|
-
subConditions.push(`json_extract(elements.value, '$.${subField}') > ?`);
|
|
122
|
-
params.push(opValue);
|
|
123
|
-
break;
|
|
124
|
-
case '$gte':
|
|
125
|
-
subConditions.push(`json_extract(elements.value, '$.${subField}') >= ?`);
|
|
126
|
-
params.push(opValue);
|
|
127
|
-
break;
|
|
128
|
-
case '$lt':
|
|
129
|
-
subConditions.push(`json_extract(elements.value, '$.${subField}') < ?`);
|
|
130
|
-
params.push(opValue);
|
|
131
|
-
break;
|
|
132
|
-
case '$lte':
|
|
133
|
-
subConditions.push(`json_extract(elements.value, '$.${subField}') <= ?`);
|
|
134
|
-
params.push(opValue);
|
|
135
|
-
break;
|
|
136
|
-
// Add other operators as needed
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return subConditions.join(' AND ');
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// Simple equality for direct value
|
|
143
|
-
params.push(subValue);
|
|
144
|
-
return `json_extract(elements.value, '$.${subField}') = ?`;
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
.join(' AND ');
|
|
148
|
-
elemMatchSubquery += elemConditions;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
// Simple equality check for the entire element
|
|
152
|
-
params.push(subFilter);
|
|
153
|
-
elemMatchSubquery += `elements.value = ?`;
|
|
154
|
-
}
|
|
155
|
-
elemMatchSubquery += ')';
|
|
156
|
-
return `${checkArrayCondition} AND ${elemMatchSubquery}`;
|
|
157
|
-
})
|
|
158
|
-
.join(' AND ');
|
|
159
|
-
conditions.push(`(${elemMatchConditions})`);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// Handle field conditions
|
|
163
|
-
if (key.startsWith('$'))
|
|
164
|
-
continue; // Skip logical operators already handled
|
|
165
|
-
const value = filter[key];
|
|
166
|
-
if (key === '_id') {
|
|
167
|
-
if (typeof value === 'string') {
|
|
168
|
-
conditions.push('_id = ?');
|
|
169
|
-
params.push(value);
|
|
170
|
-
}
|
|
171
|
-
else if (typeof value === 'object' &&
|
|
172
|
-
value !== null &&
|
|
173
|
-
value.$in) {
|
|
174
|
-
const inValues = value.$in;
|
|
175
|
-
if (inValues.length > 0) {
|
|
176
|
-
conditions.push(`_id IN (${inValues.map(() => '?').join(',')})`);
|
|
177
|
-
params.push(...inValues);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
conditions.push('1=0'); // No values in $in means nothing matches
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else if (typeof value === 'object' &&
|
|
184
|
-
value !== null &&
|
|
185
|
-
value.$ne) {
|
|
186
|
-
conditions.push('_id <> ?');
|
|
187
|
-
params.push(value.$ne);
|
|
188
|
-
}
|
|
189
|
-
// Add other _id specific operators if needed
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
const jsonPath = this.parseJsonPath(key);
|
|
193
|
-
if (typeof value === 'object' && value !== null) {
|
|
194
|
-
// Handle operators like $gt, $lt, $in, $exists, $not etc.
|
|
195
|
-
for (const op in value) {
|
|
196
|
-
const opValue = value[op];
|
|
197
|
-
switch (op) {
|
|
198
|
-
case '$eq':
|
|
199
|
-
conditions.push(`json_extract(data, ${jsonPath}) = ?`);
|
|
200
|
-
params.push(opValue);
|
|
201
|
-
break;
|
|
202
|
-
case '$ne':
|
|
203
|
-
if (opValue === null) {
|
|
204
|
-
conditions.push(`json_extract(data, ${jsonPath}) IS NOT NULL`);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
conditions.push(`json_extract(data, ${jsonPath}) != ?`);
|
|
208
|
-
params.push(opValue);
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
case '$gt':
|
|
212
|
-
conditions.push(`json_extract(data, ${jsonPath}) > ?`);
|
|
213
|
-
params.push(opValue);
|
|
214
|
-
break;
|
|
215
|
-
case '$gte':
|
|
216
|
-
conditions.push(`json_extract(data, ${jsonPath}) >= ?`);
|
|
217
|
-
params.push(opValue);
|
|
218
|
-
break;
|
|
219
|
-
case '$lt':
|
|
220
|
-
conditions.push(`json_extract(data, ${jsonPath}) < ?`);
|
|
221
|
-
params.push(opValue);
|
|
222
|
-
break;
|
|
223
|
-
case '$lte':
|
|
224
|
-
conditions.push(`json_extract(data, ${jsonPath}) <= ?`);
|
|
225
|
-
params.push(opValue);
|
|
226
|
-
break;
|
|
227
|
-
case '$in':
|
|
228
|
-
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
229
|
-
const inConditions = opValue
|
|
230
|
-
.map(() => `json_extract(data, ${jsonPath}) = ?`)
|
|
231
|
-
.join(' OR ');
|
|
232
|
-
conditions.push(`(${inConditions})`);
|
|
233
|
-
opValue.forEach((val) => params.push(val));
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
conditions.push('1=0'); // Empty $in array, nothing will match
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case '$nin':
|
|
240
|
-
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
241
|
-
const ninConditions = opValue
|
|
242
|
-
.map(() => `json_extract(data, ${jsonPath}) != ?`)
|
|
243
|
-
.join(' AND ');
|
|
244
|
-
conditions.push(`(${ninConditions})`);
|
|
245
|
-
opValue.forEach((val) => params.push(val));
|
|
246
|
-
}
|
|
247
|
-
// Empty $nin array means match everything, so no condition needed
|
|
248
|
-
break;
|
|
249
|
-
case '$exists':
|
|
250
|
-
if (opValue === true) {
|
|
251
|
-
conditions.push(`json_extract(data, ${jsonPath}) IS NOT NULL`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
conditions.push(`json_extract(data, ${jsonPath}) IS NULL`);
|
|
255
|
-
}
|
|
256
|
-
break;
|
|
257
|
-
case '$not':
|
|
258
|
-
// Handle negation of conditions
|
|
259
|
-
const notCondition = this.buildWhereClause({ [key]: opValue }, params);
|
|
260
|
-
conditions.push(`NOT (${notCondition})`);
|
|
261
|
-
break;
|
|
262
|
-
case '$all':
|
|
263
|
-
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
264
|
-
// Check if the field is an array
|
|
265
|
-
const arrayTypeCheck = `json_type(json_extract(data, ${jsonPath})) = 'array'`;
|
|
266
|
-
// For each value in the $all array, create a subquery to check if it exists in the array
|
|
267
|
-
const allConditions = opValue
|
|
268
|
-
.map(() => {
|
|
269
|
-
return `EXISTS (SELECT 1 FROM json_each(json_extract(data, ${jsonPath})) WHERE json_each.value = ?)`;
|
|
270
|
-
})
|
|
271
|
-
.join(' AND ');
|
|
272
|
-
conditions.push(`(${arrayTypeCheck} AND ${allConditions})`);
|
|
273
|
-
opValue.forEach((val) => params.push(val));
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
conditions.push('1=0'); // Empty $all array, nothing will match
|
|
277
|
-
}
|
|
278
|
-
break;
|
|
279
|
-
case '$elemMatch':
|
|
280
|
-
// Handle array element match
|
|
281
|
-
// For nested.items we need to check for conditions on the same array element
|
|
282
|
-
// Build a query that checks if there's at least one array element that satisfies all conditions
|
|
283
|
-
const arrayPath = this.parseJsonPath(key);
|
|
284
|
-
let elemMatchSubquery = `EXISTS (
|
|
285
|
-
SELECT 1
|
|
286
|
-
FROM json_each(json_extract(data, ${arrayPath})) as array_elements
|
|
287
|
-
WHERE `;
|
|
288
|
-
// Process each condition in the $elemMatch
|
|
289
|
-
if (typeof opValue === 'object' && opValue !== null) {
|
|
290
|
-
const subConditions = Object.entries(opValue)
|
|
291
|
-
.map(([sfKey, sfValue]) => {
|
|
292
|
-
if (typeof sfValue === 'object' && sfValue !== null) {
|
|
293
|
-
// Handle operators in the subfilter
|
|
294
|
-
const subOpConditions = [];
|
|
295
|
-
for (const op in sfValue) {
|
|
296
|
-
const opVal = sfValue[op];
|
|
297
|
-
switch (op) {
|
|
298
|
-
case '$eq':
|
|
299
|
-
subOpConditions.push(`json_extract(array_elements.value, '$.${sfKey}') = ?`);
|
|
300
|
-
params.push(opVal);
|
|
301
|
-
break;
|
|
302
|
-
case '$gt':
|
|
303
|
-
subOpConditions.push(`json_extract(array_elements.value, '$.${sfKey}') > ?`);
|
|
304
|
-
params.push(opVal);
|
|
305
|
-
break;
|
|
306
|
-
case '$gte':
|
|
307
|
-
subOpConditions.push(`json_extract(array_elements.value, '$.${sfKey}') >= ?`);
|
|
308
|
-
params.push(opVal);
|
|
309
|
-
break;
|
|
310
|
-
case '$lt':
|
|
311
|
-
subOpConditions.push(`json_extract(array_elements.value, '$.${sfKey}') < ?`);
|
|
312
|
-
params.push(opVal);
|
|
313
|
-
break;
|
|
314
|
-
case '$lte':
|
|
315
|
-
subOpConditions.push(`json_extract(array_elements.value, '$.${sfKey}') <= ?`);
|
|
316
|
-
params.push(opVal);
|
|
317
|
-
break;
|
|
318
|
-
// Add other operators as needed
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return subOpConditions.join(' AND ');
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// Simple equality for direct value
|
|
325
|
-
params.push(sfValue);
|
|
326
|
-
return `json_extract(array_elements.value, '$.${sfKey}') = ?`;
|
|
327
|
-
}
|
|
328
|
-
})
|
|
329
|
-
.join(' AND ');
|
|
330
|
-
elemMatchSubquery += subConditions;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
// Simple equality check for the entire element
|
|
334
|
-
params.push(opValue);
|
|
335
|
-
elemMatchSubquery += `array_elements.value = ?`;
|
|
336
|
-
}
|
|
337
|
-
elemMatchSubquery += ')';
|
|
338
|
-
conditions.push(elemMatchSubquery);
|
|
339
|
-
break;
|
|
340
|
-
// Add other operators as needed
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
// Direct equality for non-object values
|
|
346
|
-
if (value === null) {
|
|
347
|
-
conditions.push(`json_extract(data, ${jsonPath}) IS NULL`);
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
conditions.push(`json_extract(data, ${jsonPath}) = ?`);
|
|
351
|
-
params.push(value);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return conditions.length > 0 ? conditions.join(' AND ') : '1=1';
|
|
358
|
-
}
|
|
359
|
-
buildSelectQuery(filter) {
|
|
360
|
-
const params = [];
|
|
361
|
-
const whereClause = this.buildWhereClause(filter, params);
|
|
362
|
-
const sql = `SELECT _id, data FROM "${this.collectionName}" WHERE ${whereClause}`;
|
|
363
|
-
if (this.options.verbose) {
|
|
364
|
-
console.log(`SQL Query: ${sql}`);
|
|
365
|
-
console.log(`Parameters: ${JSON.stringify(params)}`);
|
|
366
|
-
}
|
|
367
|
-
return { sql, params };
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Specifies the maximum number of documents the cursor will return.
|
|
371
|
-
* @param count The number of documents to limit to.
|
|
372
|
-
* @returns The `FindCursor` instance for chaining.
|
|
373
|
-
*/
|
|
374
|
-
limit(count) {
|
|
375
|
-
if (count < 0)
|
|
376
|
-
throw new Error('Limit must be a non-negative number.');
|
|
377
|
-
this.limitCount = count;
|
|
378
|
-
return this;
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Specifies the number of documents to skip.
|
|
382
|
-
* @param count The number of documents to skip.
|
|
383
|
-
* @returns The `FindCursor` instance for chaining.
|
|
384
|
-
*/
|
|
385
|
-
skip(count) {
|
|
386
|
-
if (count < 0)
|
|
387
|
-
throw new Error('Skip must be a non-negative number.');
|
|
388
|
-
this.skipCount = count;
|
|
389
|
-
return this;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Specifies the sorting order for the documents.
|
|
393
|
-
* @param sortCriteria An object defining sort order (e.g., `{ age: -1, name: 1 }`).
|
|
394
|
-
* @returns The `FindCursor` instance for chaining.
|
|
395
|
-
*/
|
|
396
|
-
sort(sortCriteria) {
|
|
397
|
-
this.sortCriteria = sortCriteria;
|
|
398
|
-
return this;
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Specifies the fields to return (projection).
|
|
402
|
-
* @param projection An object where keys are field names and values are 1 (include) or 0 (exclude).
|
|
403
|
-
* `_id` is included by default unless explicitly excluded.
|
|
404
|
-
* @returns The `FindCursor` instance for chaining.
|
|
405
|
-
*/
|
|
406
|
-
project(projection) {
|
|
407
|
-
this.projectionFields = projection;
|
|
408
|
-
return this;
|
|
409
|
-
}
|
|
410
|
-
applyProjection(doc) {
|
|
411
|
-
if (!this.projectionFields)
|
|
412
|
-
return doc;
|
|
413
|
-
const projectedDoc = {};
|
|
414
|
-
let includeMode = true; // true if any field is 1, false if any field is 0 (excluding _id)
|
|
415
|
-
let hasExplicitInclusion = false;
|
|
416
|
-
// Determine if it's an inclusion or exclusion projection
|
|
417
|
-
for (const key in this.projectionFields) {
|
|
418
|
-
if (key === '_id')
|
|
419
|
-
continue;
|
|
420
|
-
if (this.projectionFields[key] === 1 ||
|
|
421
|
-
this.projectionFields[key] === true) {
|
|
422
|
-
hasExplicitInclusion = true;
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
if (this.projectionFields[key] === 0 ||
|
|
426
|
-
this.projectionFields[key] === false) {
|
|
427
|
-
includeMode = false;
|
|
428
|
-
// No break here, need to check all for explicit inclusions if _id is also 0
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if ((this.projectionFields._id === 0 || this.projectionFields._id === false) &&
|
|
432
|
-
!hasExplicitInclusion) {
|
|
433
|
-
// If _id is excluded and no other fields are explicitly included,
|
|
434
|
-
// it's an exclusion projection where other fields are implicitly included.
|
|
435
|
-
includeMode = false;
|
|
436
|
-
}
|
|
437
|
-
else if (hasExplicitInclusion) {
|
|
438
|
-
includeMode = true;
|
|
439
|
-
}
|
|
440
|
-
if (includeMode) {
|
|
441
|
-
// Inclusion mode
|
|
442
|
-
for (const key in this.projectionFields) {
|
|
443
|
-
if (this.projectionFields[key] === 1 ||
|
|
444
|
-
this.projectionFields[key] === true) {
|
|
445
|
-
if (key.includes('.')) {
|
|
446
|
-
// Handle nested paths for inclusion (basic implementation)
|
|
447
|
-
const path = key.split('.');
|
|
448
|
-
let current = doc;
|
|
449
|
-
let target = projectedDoc;
|
|
450
|
-
// Navigate to the last parent in the path
|
|
451
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
452
|
-
const segment = path[i];
|
|
453
|
-
if (current[segment] === undefined)
|
|
454
|
-
break;
|
|
455
|
-
if (target[segment] === undefined) {
|
|
456
|
-
target[segment] = {};
|
|
457
|
-
}
|
|
458
|
-
current = current[segment];
|
|
459
|
-
target = target[segment];
|
|
460
|
-
}
|
|
461
|
-
// Set the final property if we reached it
|
|
462
|
-
const lastSegment = path[path.length - 1];
|
|
463
|
-
if (current && current[lastSegment] !== undefined) {
|
|
464
|
-
target[lastSegment] = current[lastSegment];
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
else if (key in doc) {
|
|
468
|
-
projectedDoc[key] = doc[key];
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
// _id is included by default in inclusion mode, unless explicitly excluded
|
|
473
|
-
if (this.projectionFields._id !== 0 && this.projectionFields._id !== false && '_id' in doc) {
|
|
474
|
-
projectedDoc._id = doc._id;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
// Exclusion mode
|
|
479
|
-
Object.assign(projectedDoc, doc);
|
|
480
|
-
for (const key in this.projectionFields) {
|
|
481
|
-
if (this.projectionFields[key] === 0 ||
|
|
482
|
-
this.projectionFields[key] === false) {
|
|
483
|
-
if (key.includes('.')) {
|
|
484
|
-
// Handle nested paths for exclusion (basic implementation)
|
|
485
|
-
const path = key.split('.');
|
|
486
|
-
let current = projectedDoc;
|
|
487
|
-
// Navigate to the parent of the property to exclude
|
|
488
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
489
|
-
const segment = path[i];
|
|
490
|
-
if (current[segment] === undefined)
|
|
491
|
-
break;
|
|
492
|
-
current = current[segment];
|
|
493
|
-
}
|
|
494
|
-
// Delete the final property if we reached its parent
|
|
495
|
-
const lastSegment = path[path.length - 1];
|
|
496
|
-
if (current && current[lastSegment] !== undefined) {
|
|
497
|
-
delete current[lastSegment];
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
delete projectedDoc[key];
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return projectedDoc;
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Executes the query and returns all matching documents as an array.
|
|
510
|
-
* @returns A promise that resolves to an array of documents.
|
|
511
|
-
*/
|
|
512
|
-
async toArray() {
|
|
513
|
-
let finalSql = this.queryParts.sql;
|
|
514
|
-
const finalParams = [...this.queryParts.params];
|
|
515
|
-
if (this.sortCriteria) {
|
|
516
|
-
const sortClauses = Object.entries(this.sortCriteria).map(([field, order]) => {
|
|
517
|
-
if (field === '_id') {
|
|
518
|
-
return `_id ${order === 1 ? 'ASC' : 'DESC'}`;
|
|
519
|
-
}
|
|
520
|
-
return `json_extract(data, ${this.parseJsonPath(field)}) ${order === 1 ? 'ASC' : 'DESC'}`;
|
|
521
|
-
});
|
|
522
|
-
if (sortClauses.length > 0) {
|
|
523
|
-
finalSql += ` ORDER BY ${sortClauses.join(', ')}`;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (this.limitCount !== null) {
|
|
527
|
-
finalSql += ` LIMIT ?`;
|
|
528
|
-
finalParams.push(this.limitCount);
|
|
529
|
-
}
|
|
530
|
-
if (this.skipCount !== null) {
|
|
531
|
-
if (this.limitCount === null) {
|
|
532
|
-
// SQLite requires a LIMIT if OFFSET is used.
|
|
533
|
-
// Use a very large number if no limit is specified.
|
|
534
|
-
finalSql += ` LIMIT -1`; // Or a large number like 999999999
|
|
535
|
-
}
|
|
536
|
-
finalSql += ` OFFSET ?`;
|
|
537
|
-
finalParams.push(this.skipCount);
|
|
538
|
-
}
|
|
539
|
-
const rows = await this.db.all(finalSql, finalParams);
|
|
540
|
-
return rows.map((row) => {
|
|
541
|
-
const doc = { _id: row._id, ...JSON.parse(row.data) };
|
|
542
|
-
return this.applyProjection(doc);
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
}
|
|
2
|
+
import { FindCursor } from './cursors/findCursor.js';
|
|
546
3
|
/**
|
|
547
4
|
* MongoLiteCollection provides methods to interact with a specific SQLite table
|
|
548
5
|
* as if it were a MongoDB collection.
|