@objectql/driver-mongo 4.0.0 → 4.0.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/CHANGELOG.md +31 -3
- package/dist/index.d.ts +16 -7
- package/dist/index.js +96 -107
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +105 -111
- package/test/index.test.ts +64 -70
- package/tsconfig.tsbuildinfo +1 -1
package/src/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { Data, Driver as DriverSpec } from '@objectstack/spec';
|
|
2
|
+
type QueryAST = Data.QueryAST;
|
|
3
|
+
type SortNode = Data.SortNode;
|
|
4
|
+
type DriverInterface = DriverSpec.DriverInterface;
|
|
1
5
|
/**
|
|
2
6
|
* ObjectQL
|
|
3
7
|
* Copyright (c) 2026-present ObjectStack Inc.
|
|
@@ -7,7 +11,6 @@
|
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
13
|
import { Driver } from '@objectql/types';
|
|
10
|
-
import { DriverInterface, QueryAST, FilterNode, SortNode } from '@objectstack/spec';
|
|
11
14
|
import { MongoClient, Db, Filter, ObjectId, FindOptions } from 'mongodb';
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -43,7 +46,7 @@ export interface CommandResult {
|
|
|
43
46
|
*
|
|
44
47
|
* The driver internally converts QueryAST format to MongoDB query format.
|
|
45
48
|
*/
|
|
46
|
-
export class MongoDriver implements Driver
|
|
49
|
+
export class MongoDriver implements Driver {
|
|
47
50
|
// Driver metadata (ObjectStack-compatible)
|
|
48
51
|
public readonly name = 'MongoDriver';
|
|
49
52
|
public readonly version = '3.0.1';
|
|
@@ -52,7 +55,13 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
52
55
|
joins: false,
|
|
53
56
|
fullTextSearch: true,
|
|
54
57
|
jsonFields: true,
|
|
55
|
-
arrayFields: true
|
|
58
|
+
arrayFields: true,
|
|
59
|
+
queryFilters: true,
|
|
60
|
+
queryAggregations: true,
|
|
61
|
+
querySorting: true,
|
|
62
|
+
queryPagination: true,
|
|
63
|
+
queryWindowFunctions: false,
|
|
64
|
+
querySubqueries: false
|
|
56
65
|
};
|
|
57
66
|
|
|
58
67
|
private client: MongoClient;
|
|
@@ -140,12 +149,72 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
private mapFilters(filters: any): Filter<any> {
|
|
143
|
-
if (!filters
|
|
152
|
+
if (!filters) return {};
|
|
153
|
+
|
|
154
|
+
// If filters is an object (FilterCondition format), map id fields to _id
|
|
155
|
+
if (typeof filters === 'object' && !Array.isArray(filters)) {
|
|
156
|
+
return this.mapIdFieldsInFilter(filters);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// If filters is an array (legacy format), convert it
|
|
160
|
+
if (Array.isArray(filters) && filters.length === 0) return {};
|
|
144
161
|
|
|
145
162
|
const result = this.buildFilterConditions(filters);
|
|
146
163
|
return result;
|
|
147
164
|
}
|
|
148
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Recursively map 'id' fields to '_id' in FilterCondition objects
|
|
168
|
+
* and normalize primitive values to use $eq operator when inside logical operators
|
|
169
|
+
*/
|
|
170
|
+
private mapIdFieldsInFilter(filter: any, insideLogicalOp: boolean = false): any {
|
|
171
|
+
if (!filter || typeof filter !== 'object') {
|
|
172
|
+
return filter;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const result: any = {};
|
|
176
|
+
|
|
177
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
178
|
+
// Handle logical operators
|
|
179
|
+
if (key === '$and' || key === '$or') {
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
// Pass true to indicate we're inside a logical operator
|
|
182
|
+
result[key] = value.map(v => this.mapIdFieldsInFilter(v, true));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Map 'id' to '_id'
|
|
186
|
+
else if (key === 'id') {
|
|
187
|
+
// If value is already an object with operators, recurse
|
|
188
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
189
|
+
result['_id'] = this.mapIdFieldsInFilter(value, insideLogicalOp);
|
|
190
|
+
} else {
|
|
191
|
+
// Primitive value - wrap with $eq only if inside logical operator
|
|
192
|
+
result['_id'] = insideLogicalOp ? { $eq: value } : value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Skip MongoDB operator keys (starting with $) - but still recurse for nested mappings
|
|
196
|
+
else if (key.startsWith('$')) {
|
|
197
|
+
// Recursively process to handle nested objects that might contain 'id' fields
|
|
198
|
+
if (typeof value === 'object' && value !== null) {
|
|
199
|
+
result[key] = this.mapIdFieldsInFilter(value, insideLogicalOp);
|
|
200
|
+
} else {
|
|
201
|
+
result[key] = value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Recursively handle nested objects (already operator-wrapped values)
|
|
205
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
206
|
+
result[key] = this.mapIdFieldsInFilter(value, insideLogicalOp);
|
|
207
|
+
}
|
|
208
|
+
// Primitive field values
|
|
209
|
+
else {
|
|
210
|
+
// Wrap with $eq only if inside a logical operator
|
|
211
|
+
result[key] = insideLogicalOp ? { $eq: value } : value;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
149
218
|
/**
|
|
150
219
|
* Build MongoDB filter conditions from ObjectQL filter array.
|
|
151
220
|
* Supports nested filter groups and logical operators (AND/OR).
|
|
@@ -248,68 +317,45 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
248
317
|
* QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
|
|
249
318
|
* QueryAST uses 'aggregations', while legacy uses 'aggregate'.
|
|
250
319
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const normalized: any = { ...query };
|
|
320
|
+
async find(objectName: string, query: any, options?: any): Promise<any[]> {
|
|
321
|
+
const collection = await this.getCollection(objectName);
|
|
255
322
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
323
|
+
// Handle both new format (where) and legacy format (filters)
|
|
324
|
+
const filterCondition = query.where || query.filters;
|
|
325
|
+
const filter = this.mapFilters(filterCondition);
|
|
260
326
|
|
|
261
|
-
|
|
262
|
-
if (normalized.aggregations !== undefined && normalized.aggregate === undefined) {
|
|
263
|
-
// Convert QueryAST aggregations format to legacy aggregate format
|
|
264
|
-
normalized.aggregate = normalized.aggregations.map((agg: any) => ({
|
|
265
|
-
func: agg.function || agg.func,
|
|
266
|
-
field: agg.field,
|
|
267
|
-
alias: agg.alias
|
|
268
|
-
}));
|
|
269
|
-
}
|
|
327
|
+
const findOptions: FindOptions = {};
|
|
270
328
|
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const firstSort = normalized.sort[0];
|
|
275
|
-
if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
|
|
276
|
-
// Convert from QueryAST format {field, order} to internal format [field, order]
|
|
277
|
-
normalized.sort = normalized.sort.map((item: any) => [
|
|
278
|
-
item.field,
|
|
279
|
-
item.order || item.direction || item.dir || 'asc'
|
|
280
|
-
]);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
329
|
+
// Handle pagination - support both new format (offset/limit) and legacy format (skip/top)
|
|
330
|
+
const offsetValue = query.offset ?? query.skip;
|
|
331
|
+
const limitValue = query.limit ?? query.top;
|
|
283
332
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
async find(objectName: string, query: any, options?: any): Promise<any[]> {
|
|
288
|
-
const normalizedQuery = this.normalizeQuery(query);
|
|
289
|
-
const collection = await this.getCollection(objectName);
|
|
290
|
-
const filter = this.mapFilters(normalizedQuery.filters);
|
|
333
|
+
if (offsetValue !== undefined) findOptions.skip = offsetValue;
|
|
334
|
+
if (limitValue !== undefined) findOptions.limit = limitValue;
|
|
291
335
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
if (normalizedQuery.sort) {
|
|
296
|
-
// map [['field', 'desc']] to { field: -1 }
|
|
336
|
+
// Handle sort - support both new format (orderBy) and legacy format (sort)
|
|
337
|
+
const sortArray = query.orderBy || query.sort;
|
|
338
|
+
if (sortArray && Array.isArray(sortArray)) {
|
|
297
339
|
findOptions.sort = {};
|
|
298
|
-
for (const
|
|
340
|
+
for (const item of sortArray) {
|
|
341
|
+
// Support both {field, order} object format and [field, order] array format
|
|
342
|
+
const field = item.field || item[0];
|
|
343
|
+
const order = item.order || item[1] || 'asc';
|
|
299
344
|
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
300
345
|
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
301
346
|
(findOptions.sort as any)[dbField] = order === 'desc' ? -1 : 1;
|
|
302
347
|
}
|
|
303
348
|
}
|
|
304
|
-
|
|
349
|
+
|
|
350
|
+
if (query.fields && query.fields.length > 0) {
|
|
305
351
|
findOptions.projection = {};
|
|
306
|
-
for (const field of
|
|
352
|
+
for (const field of query.fields) {
|
|
307
353
|
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
308
354
|
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
309
355
|
(findOptions.projection as any)[dbField] = 1;
|
|
310
356
|
}
|
|
311
357
|
// Explicitly exclude _id if 'id' is not in the requested fields
|
|
312
|
-
const hasIdField =
|
|
358
|
+
const hasIdField = query.fields.some((f: string) => f === 'id' || f === '_id');
|
|
313
359
|
if (!hasIdField) {
|
|
314
360
|
(findOptions.projection as any)._id = 0;
|
|
315
361
|
}
|
|
@@ -373,9 +419,12 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
373
419
|
|
|
374
420
|
async count(objectName: string, filters: any, options?: any): Promise<number> {
|
|
375
421
|
const collection = await this.getCollection(objectName);
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
422
|
+
// Handle both filter objects and query objects
|
|
423
|
+
let actualFilters = filters;
|
|
424
|
+
if (filters && (filters.where || filters.filters)) {
|
|
425
|
+
// It's a query object with 'where' or 'filters' property
|
|
426
|
+
actualFilters = filters.where || filters.filters;
|
|
427
|
+
}
|
|
379
428
|
const filter = this.mapFilters(actualFilters);
|
|
380
429
|
return await collection.countDocuments(filter);
|
|
381
430
|
}
|
|
@@ -442,19 +491,9 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
442
491
|
* @returns Query results with value and count
|
|
443
492
|
*/
|
|
444
493
|
async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const legacyQuery: any = {
|
|
449
|
-
fields: ast.fields,
|
|
450
|
-
filters: this.convertFilterNodeToLegacy(ast.filters),
|
|
451
|
-
sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
|
|
452
|
-
limit: ast.top,
|
|
453
|
-
skip: ast.skip,
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
// Use existing find method
|
|
457
|
-
const results = await this.find(objectName, legacyQuery, options);
|
|
494
|
+
// QueryAST is now the same format as our internal query
|
|
495
|
+
// Just pass it directly to find
|
|
496
|
+
const results = await this.find(ast.object || '', ast, options);
|
|
458
497
|
|
|
459
498
|
return {
|
|
460
499
|
value: results,
|
|
@@ -565,54 +604,9 @@ export class MongoDriver implements Driver, DriverInterface {
|
|
|
565
604
|
}
|
|
566
605
|
|
|
567
606
|
/**
|
|
568
|
-
* Convert
|
|
607
|
+
* Convert FilterCondition (QueryAST format) to legacy filter array format
|
|
569
608
|
* This allows reuse of existing filter logic while supporting new QueryAST
|
|
570
609
|
*
|
|
571
|
-
* @private
|
|
572
|
-
*/
|
|
573
|
-
private convertFilterNodeToLegacy(node?: FilterNode): any {
|
|
574
|
-
if (!node) return undefined;
|
|
575
|
-
|
|
576
|
-
switch (node.type) {
|
|
577
|
-
case 'comparison':
|
|
578
|
-
// Convert comparison node to [field, operator, value] format
|
|
579
|
-
const operator = node.operator || '=';
|
|
580
|
-
return [[node.field, operator, node.value]];
|
|
581
|
-
|
|
582
|
-
case 'and':
|
|
583
|
-
case 'or':
|
|
584
|
-
// Convert AND/OR node to array with separator
|
|
585
|
-
if (!node.children || node.children.length === 0) return undefined;
|
|
586
|
-
const results: any[] = [];
|
|
587
|
-
const separator = node.type; // 'and' or 'or'
|
|
588
|
-
|
|
589
|
-
for (const child of node.children) {
|
|
590
|
-
const converted = this.convertFilterNodeToLegacy(child);
|
|
591
|
-
if (converted) {
|
|
592
|
-
if (results.length > 0) {
|
|
593
|
-
results.push(separator);
|
|
594
|
-
}
|
|
595
|
-
results.push(...(Array.isArray(converted) ? converted : [converted]));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
return results.length > 0 ? results : undefined;
|
|
599
|
-
|
|
600
|
-
case 'not':
|
|
601
|
-
// NOT is not directly supported in the legacy filter format
|
|
602
|
-
// MongoDB supports $not, but legacy array format doesn't have a NOT operator
|
|
603
|
-
// Use native MongoDB queries with $not instead:
|
|
604
|
-
// Example: { field: { $not: { $eq: value } } }
|
|
605
|
-
throw new Error(
|
|
606
|
-
'NOT filters are not supported in legacy filter format. ' +
|
|
607
|
-
'Use native MongoDB queries with $not operator instead. ' +
|
|
608
|
-
'Example: { field: { $not: { $eq: value } } }'
|
|
609
|
-
);
|
|
610
|
-
|
|
611
|
-
default:
|
|
612
|
-
return undefined;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
610
|
/**
|
|
617
611
|
* Execute command (alternative signature for compatibility)
|
|
618
612
|
*
|
package/test/index.test.ts
CHANGED
|
@@ -62,23 +62,16 @@ describe('MongoDriver', () => {
|
|
|
62
62
|
|
|
63
63
|
it('should find objects with query', async () => {
|
|
64
64
|
const query = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
where: { age: { $gt: 18 } },
|
|
66
|
+
orderBy: [{ field: 'name', order: 'asc' as const }],
|
|
67
|
+
offset: 10,
|
|
68
68
|
limit: 5
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
await driver.find('users', query);
|
|
72
72
|
|
|
73
|
-
// Debugging what was actually called
|
|
74
|
-
// console.log('Find calls:', mockCollection.find.mock.calls);
|
|
75
|
-
|
|
76
73
|
expect(mockDb.collection).toHaveBeenCalledWith('users');
|
|
77
74
|
|
|
78
|
-
// We expect: find(filter, options)
|
|
79
|
-
// filter = { $and: [{ age: { $gt: 18 } }] }
|
|
80
|
-
// options = { limit: 5, skip: 10, sort: { name: 1 } }
|
|
81
|
-
|
|
82
75
|
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
83
76
|
{ age: { $gt: 18 } },
|
|
84
77
|
expect.objectContaining({
|
|
@@ -92,7 +85,12 @@ describe('MongoDriver', () => {
|
|
|
92
85
|
|
|
93
86
|
it('should handle OR filters', async () => {
|
|
94
87
|
const query = {
|
|
95
|
-
|
|
88
|
+
where: {
|
|
89
|
+
$or: [
|
|
90
|
+
{ age: { $gt: 18 } },
|
|
91
|
+
{ role: 'admin' }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
96
94
|
};
|
|
97
95
|
await driver.find('users', query);
|
|
98
96
|
|
|
@@ -109,7 +107,7 @@ describe('MongoDriver', () => {
|
|
|
109
107
|
|
|
110
108
|
it('should map "id" field to "_id" in filters', async () => {
|
|
111
109
|
const query = {
|
|
112
|
-
|
|
110
|
+
where: { id: '12345' }
|
|
113
111
|
};
|
|
114
112
|
await driver.find('users', query);
|
|
115
113
|
|
|
@@ -121,7 +119,7 @@ describe('MongoDriver', () => {
|
|
|
121
119
|
|
|
122
120
|
it('should map "id" to "_id" in sorting', async () => {
|
|
123
121
|
const query = {
|
|
124
|
-
|
|
122
|
+
orderBy: [{ field: 'id', order: 'desc' as const }]
|
|
125
123
|
};
|
|
126
124
|
await driver.find('users', query);
|
|
127
125
|
|
|
@@ -192,7 +190,7 @@ describe('MongoDriver', () => {
|
|
|
192
190
|
// Backward compatibility tests for legacy '_id' usage
|
|
193
191
|
it('should accept "_id" field in filters for backward compatibility', async () => {
|
|
194
192
|
const query = {
|
|
195
|
-
|
|
193
|
+
where: { _id: '12345' }
|
|
196
194
|
};
|
|
197
195
|
await driver.find('users', query);
|
|
198
196
|
|
|
@@ -204,7 +202,7 @@ describe('MongoDriver', () => {
|
|
|
204
202
|
|
|
205
203
|
it('should accept "_id" in sorting for backward compatibility', async () => {
|
|
206
204
|
const query = {
|
|
207
|
-
|
|
205
|
+
orderBy: [{ field: '_id', order: 'asc' as const }]
|
|
208
206
|
};
|
|
209
207
|
await driver.find('users', query);
|
|
210
208
|
|
|
@@ -237,19 +235,22 @@ describe('MongoDriver', () => {
|
|
|
237
235
|
|
|
238
236
|
it('should handle nested filter groups', async () => {
|
|
239
237
|
const query = {
|
|
240
|
-
|
|
241
|
-
[
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
where: {
|
|
239
|
+
$or: [
|
|
240
|
+
{
|
|
241
|
+
$and: [
|
|
242
|
+
{ status: 'completed' },
|
|
243
|
+
{ amount: { $gt: 100 } }
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
$and: [
|
|
248
|
+
{ customer: 'Alice' },
|
|
249
|
+
{ status: 'pending' }
|
|
250
|
+
]
|
|
251
|
+
}
|
|
251
252
|
]
|
|
252
|
-
|
|
253
|
+
}
|
|
253
254
|
};
|
|
254
255
|
await driver.find('orders', query);
|
|
255
256
|
|
|
@@ -271,19 +272,22 @@ describe('MongoDriver', () => {
|
|
|
271
272
|
|
|
272
273
|
it('should handle deeply nested filter groups', async () => {
|
|
273
274
|
const query = {
|
|
274
|
-
|
|
275
|
-
[
|
|
276
|
-
|
|
277
|
-
[
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
275
|
+
where: {
|
|
276
|
+
$and: [
|
|
277
|
+
{
|
|
278
|
+
$or: [
|
|
279
|
+
{
|
|
280
|
+
$and: [
|
|
281
|
+
{ age: { $gt: 22 } },
|
|
282
|
+
{ status: 'active' }
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
{ role: 'admin' }
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
{ name: { $ne: 'Bob' } }
|
|
289
|
+
]
|
|
290
|
+
}
|
|
287
291
|
};
|
|
288
292
|
await driver.find('users', query);
|
|
289
293
|
|
|
@@ -313,13 +317,17 @@ describe('MongoDriver', () => {
|
|
|
313
317
|
|
|
314
318
|
it('should handle nested groups with implicit AND', async () => {
|
|
315
319
|
const query = {
|
|
316
|
-
|
|
317
|
-
[
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
where: {
|
|
321
|
+
$and: [
|
|
322
|
+
{
|
|
323
|
+
$and: [
|
|
324
|
+
{ status: 'active' },
|
|
325
|
+
{ role: 'admin' }
|
|
326
|
+
]
|
|
327
|
+
},
|
|
328
|
+
{ age: { $gt: 25 } }
|
|
329
|
+
]
|
|
330
|
+
}
|
|
323
331
|
};
|
|
324
332
|
await driver.find('users', query);
|
|
325
333
|
|
|
@@ -341,14 +349,11 @@ describe('MongoDriver', () => {
|
|
|
341
349
|
const ast = {
|
|
342
350
|
object: 'users',
|
|
343
351
|
fields: ['name', 'email'],
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
field: 'status',
|
|
347
|
-
operator: '=',
|
|
348
|
-
value: 'active'
|
|
352
|
+
where: {
|
|
353
|
+
status: 'active'
|
|
349
354
|
},
|
|
350
|
-
|
|
351
|
-
|
|
355
|
+
limit: 10,
|
|
356
|
+
offset: 0
|
|
352
357
|
};
|
|
353
358
|
|
|
354
359
|
mockCollection.toArray.mockResolvedValue([
|
|
@@ -366,21 +371,10 @@ describe('MongoDriver', () => {
|
|
|
366
371
|
it('should handle complex QueryAST with AND filters', async () => {
|
|
367
372
|
const ast = {
|
|
368
373
|
object: 'users',
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
{
|
|
373
|
-
type: 'comparison' as const,
|
|
374
|
-
field: 'status',
|
|
375
|
-
operator: '=',
|
|
376
|
-
value: 'active'
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
type: 'comparison' as const,
|
|
380
|
-
field: 'age',
|
|
381
|
-
operator: '>',
|
|
382
|
-
value: 18
|
|
383
|
-
}
|
|
374
|
+
where: {
|
|
375
|
+
$and: [
|
|
376
|
+
{ status: 'active' },
|
|
377
|
+
{ age: { $gt: 18 } }
|
|
384
378
|
]
|
|
385
379
|
}
|
|
386
380
|
};
|
|
@@ -396,7 +390,7 @@ describe('MongoDriver', () => {
|
|
|
396
390
|
it('should handle QueryAST with sort', async () => {
|
|
397
391
|
const ast = {
|
|
398
392
|
object: 'users',
|
|
399
|
-
|
|
393
|
+
orderBy: [
|
|
400
394
|
{ field: 'name', order: 'asc' as const }
|
|
401
395
|
]
|
|
402
396
|
};
|