@mastra/upstash 0.14.4 → 0.14.6-alpha.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.
- package/CHANGELOG.md +18 -0
- package/package.json +20 -7
- package/.turbo/turbo-build.log +0 -4
- package/PAGINATION.md +0 -397
- package/docker-compose.yaml +0 -15
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -3
- package/src/storage/domains/legacy-evals/index.ts +0 -279
- package/src/storage/domains/memory/index.ts +0 -1039
- package/src/storage/domains/operations/index.ts +0 -168
- package/src/storage/domains/scores/index.ts +0 -231
- package/src/storage/domains/traces/index.ts +0 -172
- package/src/storage/domains/utils.ts +0 -65
- package/src/storage/domains/workflows/index.ts +0 -280
- package/src/storage/index.test.ts +0 -13
- package/src/storage/index.ts +0 -404
- package/src/vector/filter.test.ts +0 -558
- package/src/vector/filter.ts +0 -260
- package/src/vector/hybrid.test.ts +0 -1455
- package/src/vector/index.test.ts +0 -1205
- package/src/vector/index.ts +0 -291
- package/src/vector/prompt.ts +0 -77
- package/src/vector/types.ts +0 -26
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { UpstashVectorFilter } from './filter';
|
|
4
|
-
import { UpstashFilterTranslator } from './filter';
|
|
5
|
-
|
|
6
|
-
describe('UpstashFilterTranslator', () => {
|
|
7
|
-
let translator: UpstashFilterTranslator;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
translator = new UpstashFilterTranslator();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('handles empty/undefined filters', () => {
|
|
14
|
-
expect(translator.translate(undefined)).toBeUndefined();
|
|
15
|
-
expect(translator.translate({})).toBeUndefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('translates primitive equality', () => {
|
|
19
|
-
expect(translator.translate({ city: 'Istanbul' })).toBe("city = 'Istanbul'");
|
|
20
|
-
expect(translator.translate({ population: 15460000 })).toBe('population = 15460000');
|
|
21
|
-
expect(translator.translate({ is_capital: false })).toBe('is_capital = false');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('translates nested paths', () => {
|
|
25
|
-
expect(translator.translate({ 'geography.continent': 'Asia' })).toBe("geography.continent = 'Asia'");
|
|
26
|
-
expect(translator.translate({ geography: { continent: 'Asia' } } as any)).toBe("geography.continent = 'Asia'");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('translates comparison operators', () => {
|
|
30
|
-
expect(translator.translate({ population: { $gt: 1000000 } })).toBe('population > 1000000');
|
|
31
|
-
expect(translator.translate({ population: { $gte: 1000000 } })).toBe('population >= 1000000');
|
|
32
|
-
expect(translator.translate({ population: { $lt: 2000000 } })).toBe('population < 2000000');
|
|
33
|
-
expect(translator.translate({ population: { $lte: 2000000 } })).toBe('population <= 2000000');
|
|
34
|
-
expect(translator.translate({ population: { $ne: 1000000 } })).toBe('population != 1000000');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('translates array operators', () => {
|
|
38
|
-
expect(translator.translate({ country: { $in: ['Turkey', 'Germany'] } })).toBe("country IN ('Turkey', 'Germany')");
|
|
39
|
-
expect(translator.translate({ country: { $nin: ['USA', 'Canada'] } })).toBe("country NOT IN ('USA', 'Canada')");
|
|
40
|
-
expect(translator.translate({ 'economy.major_industries': { $contains: 'Tourism' } })).toBe(
|
|
41
|
-
"economy.major_industries CONTAINS 'Tourism'",
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('translates existence checks', () => {
|
|
46
|
-
expect(translator.translate({ coordinates: { $exists: true } })).toBe('HAS FIELD coordinates');
|
|
47
|
-
expect(translator.translate({ coordinates: { $exists: false } })).toBe('HAS NOT FIELD coordinates');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('translates glob patterns', () => {
|
|
51
|
-
expect(translator.translate({ city: { $regex: '?[sz]*[^m-z]' } })).toBe("city GLOB '?[sz]*[^m-z]'");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('translates logical operators', () => {
|
|
55
|
-
expect(
|
|
56
|
-
translator.translate({
|
|
57
|
-
$and: [{ population: { $gte: 1000000 } }, { 'geography.continent': 'Asia' }],
|
|
58
|
-
}),
|
|
59
|
-
).toBe("(population >= 1000000 AND geography.continent = 'Asia')");
|
|
60
|
-
|
|
61
|
-
expect(
|
|
62
|
-
translator.translate({
|
|
63
|
-
$or: [{ country: 'Turkey' }, { country: 'Germany' }],
|
|
64
|
-
}),
|
|
65
|
-
).toBe("(country = 'Turkey' OR country = 'Germany')");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('translates complex nested conditions', () => {
|
|
69
|
-
const filter: UpstashVectorFilter = {
|
|
70
|
-
$and: [
|
|
71
|
-
{ population: { $gte: 1000000 } },
|
|
72
|
-
{ 'geography.continent': 'Asia' },
|
|
73
|
-
{
|
|
74
|
-
$or: [{ 'economy.major_industries': { $contains: 'Tourism' } }, { is_capital: true }],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
expect(translator.translate(filter)).toBe(
|
|
80
|
-
"(population >= 1000000 AND geography.continent = 'Asia' AND " +
|
|
81
|
-
"(economy.major_industries CONTAINS 'Tourism' OR is_capital = true))",
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('operator combinations', () => {
|
|
86
|
-
it('combines multiple comparison operators', () => {
|
|
87
|
-
expect(
|
|
88
|
-
translator.translate({
|
|
89
|
-
population: { $gte: 1000000, $lte: 2000000 },
|
|
90
|
-
}),
|
|
91
|
-
).toBe('(population >= 1000000 AND population <= 2000000)');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('combines array operators with comparisons', () => {
|
|
95
|
-
expect(
|
|
96
|
-
translator.translate({
|
|
97
|
-
country: { $in: ['Turkey', 'Germany'] },
|
|
98
|
-
population: { $gt: 1000000 },
|
|
99
|
-
}),
|
|
100
|
-
).toBe("(country IN ('Turkey', 'Germany') AND population > 1000000)");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('combines exists with other operators', () => {
|
|
104
|
-
expect(
|
|
105
|
-
translator.translate({
|
|
106
|
-
coordinates: { $exists: true },
|
|
107
|
-
population: { $gt: 1000000 },
|
|
108
|
-
'economy.industries': { $contains: 'Tech' },
|
|
109
|
-
}),
|
|
110
|
-
).toBe("(HAS FIELD coordinates AND population > 1000000 AND economy.industries CONTAINS 'Tech')");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('combines glob with other operators', () => {
|
|
114
|
-
expect(
|
|
115
|
-
translator.translate({
|
|
116
|
-
city: { $regex: 'A*' },
|
|
117
|
-
population: { $gt: 1000000 },
|
|
118
|
-
country: { $in: ['Turkey', 'Germany'] },
|
|
119
|
-
}),
|
|
120
|
-
).toBe("(city GLOB 'A*' AND population > 1000000 AND country IN ('Turkey', 'Germany'))");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('translates $nor', () => {
|
|
124
|
-
expect(
|
|
125
|
-
translator.translate({
|
|
126
|
-
$nor: [{ type: 'food' }, { price: { $lt: 10 } }],
|
|
127
|
-
}),
|
|
128
|
-
).toBe("(type != 'food' AND price >= 10)");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('translates $all', () => {
|
|
132
|
-
expect(
|
|
133
|
-
translator.translate({
|
|
134
|
-
tags: { $all: ['red', 'round'] },
|
|
135
|
-
}),
|
|
136
|
-
).toBe("(tags CONTAINS 'red' AND tags CONTAINS 'round')");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('translates complex combination', () => {
|
|
140
|
-
expect(
|
|
141
|
-
translator.translate({
|
|
142
|
-
$and: [
|
|
143
|
-
{ name: { $regex: 'A.*' } },
|
|
144
|
-
{ $nor: [{ type: 'food' }, { category: 'toys' }] },
|
|
145
|
-
{ tags: { $all: ['premium', 'new'] } },
|
|
146
|
-
],
|
|
147
|
-
}),
|
|
148
|
-
).toBe(
|
|
149
|
-
"(name GLOB 'A.*' AND " +
|
|
150
|
-
"(type != 'food' AND category != 'toys') AND " +
|
|
151
|
-
"(tags CONTAINS 'premium' AND tags CONTAINS 'new'))",
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe('complex scenarios', () => {
|
|
157
|
-
it('deeply nested logical operators', () => {
|
|
158
|
-
const filter: UpstashVectorFilter = {
|
|
159
|
-
$or: [
|
|
160
|
-
{
|
|
161
|
-
$and: [
|
|
162
|
-
{ population: { $gte: 1000000 } },
|
|
163
|
-
{ 'geography.continent': 'Asia' },
|
|
164
|
-
{
|
|
165
|
-
$or: [
|
|
166
|
-
{ 'economy.industries': { $contains: 'Tech' } },
|
|
167
|
-
{ 'economy.industries': { $contains: 'Finance' } },
|
|
168
|
-
],
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
$and: [{ 'geography.continent': 'Europe' }, { is_capital: true }, { population: { $lt: 5000000 } }],
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
expect(translator.translate(filter)).toBe(
|
|
179
|
-
"((population >= 1000000 AND geography.continent = 'Asia' AND " +
|
|
180
|
-
"(economy.industries CONTAINS 'Tech' OR economy.industries CONTAINS 'Finance')) OR " +
|
|
181
|
-
"(geography.continent = 'Europe' AND is_capital = true AND population < 5000000))",
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('mixed array and nested object conditions', () => {
|
|
186
|
-
const filter = {
|
|
187
|
-
$and: [
|
|
188
|
-
{
|
|
189
|
-
'economy.sectors': {
|
|
190
|
-
$in: ['Technology', 'Finance', 'Tourism'],
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
'geography.coordinates': {
|
|
195
|
-
latitude: { $gte: 35.5 },
|
|
196
|
-
longitude: { $lte: 45 },
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
$or: [
|
|
201
|
-
{ population: { $gte: 1000000 } },
|
|
202
|
-
{
|
|
203
|
-
$and: [{ is_capital: true }, { 'economy.gdp': { $gte: 50000 } }],
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
expect(translator.translate(filter)).toBe(
|
|
211
|
-
"(economy.sectors IN ('Technology', 'Finance', 'Tourism') AND " +
|
|
212
|
-
'(geography.coordinates.latitude >= 35.5 AND geography.coordinates.longitude <= 45) AND ' +
|
|
213
|
-
'(population >= 1000000 OR (is_capital = true AND economy.gdp >= 50000)))',
|
|
214
|
-
);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('complex filtering with all operator types', () => {
|
|
218
|
-
const filter: UpstashVectorFilter = {
|
|
219
|
-
$and: [
|
|
220
|
-
{
|
|
221
|
-
$or: [{ name: { $regex: 'San*' } }, { name: { $regex: 'New*' } }],
|
|
222
|
-
},
|
|
223
|
-
{ population: { $gte: 500000, $lte: 10000000 } },
|
|
224
|
-
{ 'coordinates.latitude': { $exists: true } },
|
|
225
|
-
{
|
|
226
|
-
$or: [
|
|
227
|
-
{ 'economy.industries': { $contains: 'Tech' } },
|
|
228
|
-
{
|
|
229
|
-
$and: [
|
|
230
|
-
{ 'economy.industries': { $contains: 'Tourism' } },
|
|
231
|
-
{ 'climate.type': { $in: ['Mediterranean', 'Tropical'] } },
|
|
232
|
-
],
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
'demographics.languages': {
|
|
238
|
-
$nin: ['French', 'German'],
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
expect(translator.translate(filter)).toBe(
|
|
245
|
-
"((name GLOB 'San*' OR name GLOB 'New*') AND " +
|
|
246
|
-
'(population >= 500000 AND population <= 10000000) AND ' +
|
|
247
|
-
'HAS FIELD coordinates.latitude AND ' +
|
|
248
|
-
"(economy.industries CONTAINS 'Tech' OR " +
|
|
249
|
-
"(economy.industries CONTAINS 'Tourism' AND climate.type IN ('Mediterranean', 'Tropical'))) AND " +
|
|
250
|
-
"demographics.languages NOT IN ('French', 'German'))",
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('edge cases', () => {
|
|
256
|
-
it('handles empty arrays', () => {
|
|
257
|
-
expect(translator.translate({ tags: [] })).toBe('(HAS FIELD empty AND HAS NOT FIELD empty)');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('handles multiple empty conditions in logical operators', () => {
|
|
261
|
-
expect(
|
|
262
|
-
translator.translate({
|
|
263
|
-
$and: [{ field2: [] }],
|
|
264
|
-
}),
|
|
265
|
-
).toBe('((HAS FIELD empty AND HAS NOT FIELD empty))');
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('handles special characters in field names', () => {
|
|
269
|
-
expect(
|
|
270
|
-
translator.translate({
|
|
271
|
-
'field.with.dots': 'value',
|
|
272
|
-
field_with_underscores: 'value',
|
|
273
|
-
}),
|
|
274
|
-
).toBe("(field.with.dots = 'value' AND field_with_underscores = 'value')");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('handles special characters in string values', () => {
|
|
278
|
-
expect(
|
|
279
|
-
translator.translate({
|
|
280
|
-
field: "value'with'quotes",
|
|
281
|
-
}),
|
|
282
|
-
).toBe('field = "value\'with\'quotes"');
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('NOT operators', () => {
|
|
287
|
-
it('translates not equals', () => {
|
|
288
|
-
expect(translator.translate({ field: { $ne: 'value' } })).toBe("field != 'value'");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('translates not in array', () => {
|
|
292
|
-
expect(translator.translate({ field: { $nin: ['a', 'b'] } })).toBe("field NOT IN ('a', 'b')");
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('translate $not with direct object value', () => {
|
|
296
|
-
expect(translator.translate({ $not: { field: true } })).toBe('field != true');
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('translates $not with operator', () => {
|
|
300
|
-
expect(
|
|
301
|
-
translator.translate({
|
|
302
|
-
field: { $not: { $eq: 'value' } },
|
|
303
|
-
}),
|
|
304
|
-
).toBe("field != 'value'");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it('translates not contains', () => {
|
|
308
|
-
expect(
|
|
309
|
-
translator.translate({
|
|
310
|
-
field: { $not: { $contains: 'value' } },
|
|
311
|
-
}),
|
|
312
|
-
).toBe("field NOT CONTAINS 'value'");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('translates not glob', () => {
|
|
316
|
-
expect(
|
|
317
|
-
translator.translate({
|
|
318
|
-
field: { $not: { $regex: 'a*' } },
|
|
319
|
-
}),
|
|
320
|
-
).toBe("field NOT GLOB 'a*'");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('translates has not field', () => {
|
|
324
|
-
expect(translator.translate({ field: { $exists: false } })).toBe('HAS NOT FIELD field');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('translates $not with $exists', () => {
|
|
328
|
-
expect(translator.translate({ $not: { field: { $exists: true } } })).toBe('HAS NOT FIELD field');
|
|
329
|
-
expect(translator.translate({ field: { $not: { $exists: false } } })).toBe('HAS FIELD field');
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('translates $not operator with comparison', () => {
|
|
333
|
-
expect(
|
|
334
|
-
translator.translate({
|
|
335
|
-
field: { $not: { $gt: 100 } },
|
|
336
|
-
}),
|
|
337
|
-
).toBe('field <= 100');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('translates $not operator with multiple conditions', () => {
|
|
341
|
-
expect(
|
|
342
|
-
translator.translate({
|
|
343
|
-
$not: {
|
|
344
|
-
$and: [{ field: { $in: ['a', 'b'] } }, { field: { $gt: 100 } }],
|
|
345
|
-
},
|
|
346
|
-
}),
|
|
347
|
-
).toBe("(field NOT IN ('a', 'b') OR field <= 100)");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('translates $not with $nor', () => {
|
|
351
|
-
expect(
|
|
352
|
-
translator.translate({
|
|
353
|
-
$not: {
|
|
354
|
-
$nor: [{ field: { $in: ['a', 'b'] } }, { field: { $gt: 100 } }],
|
|
355
|
-
},
|
|
356
|
-
}),
|
|
357
|
-
).toBe("(field IN ('a', 'b') OR field > 100)");
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
describe('array access', () => {
|
|
362
|
-
it('handles deeply nested array access', () => {
|
|
363
|
-
expect(
|
|
364
|
-
translator.translate({
|
|
365
|
-
'array.0.nested.1.field': 'value',
|
|
366
|
-
}),
|
|
367
|
-
).toBe("array.0.nested.1.field = 'value'");
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe('multiple NOT combinations', () => {
|
|
372
|
-
it('handles multiple NOT combinations', () => {
|
|
373
|
-
expect(
|
|
374
|
-
translator.translate({
|
|
375
|
-
$and: [
|
|
376
|
-
{ field1: { $not: { $contains: 'a' } } },
|
|
377
|
-
{ field2: { $not: { $regex: 'b*' } } },
|
|
378
|
-
{ field3: { $nin: ['c', 'd'] } },
|
|
379
|
-
],
|
|
380
|
-
}),
|
|
381
|
-
).toBe("(field1 NOT CONTAINS 'a' AND field2 NOT GLOB 'b*' AND field3 NOT IN ('c', 'd'))");
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
describe('null and undefined handling', () => {
|
|
386
|
-
it('handles null in comparisons', () => {
|
|
387
|
-
expect(() => translator.translate({ value: null })).toThrow();
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it('handles null in arrays', () => {
|
|
391
|
-
expect(() =>
|
|
392
|
-
translator.translate({
|
|
393
|
-
field: { $in: [null, 'value'] },
|
|
394
|
-
}),
|
|
395
|
-
).toThrow();
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it('handles undefined values', () => {
|
|
399
|
-
expect(() =>
|
|
400
|
-
translator.translate({
|
|
401
|
-
field: undefined,
|
|
402
|
-
}),
|
|
403
|
-
).toThrow();
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
describe('empty conditions', () => {
|
|
408
|
-
it('handles empty AND', () => {
|
|
409
|
-
expect(
|
|
410
|
-
translator.translate({
|
|
411
|
-
$and: [],
|
|
412
|
-
}),
|
|
413
|
-
).toBe('(HAS FIELD empty OR HAS NOT FIELD empty)');
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it('handles empty OR', () => {
|
|
417
|
-
expect(
|
|
418
|
-
translator.translate({
|
|
419
|
-
$or: [],
|
|
420
|
-
}),
|
|
421
|
-
).toBe('(HAS FIELD empty AND HAS NOT FIELD empty)');
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('handles empty IN', () => {
|
|
425
|
-
expect(
|
|
426
|
-
translator.translate({
|
|
427
|
-
field: { $in: [] },
|
|
428
|
-
}),
|
|
429
|
-
).toBe('(HAS FIELD empty AND HAS NOT FIELD empty)');
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
describe('number handling', () => {
|
|
434
|
-
it('handles zero', () => {
|
|
435
|
-
expect(
|
|
436
|
-
translator.translate({
|
|
437
|
-
field: 0,
|
|
438
|
-
}),
|
|
439
|
-
).toBe('field = 0');
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it('handles negative numbers', () => {
|
|
443
|
-
expect(
|
|
444
|
-
translator.translate({
|
|
445
|
-
field: -123.45,
|
|
446
|
-
}),
|
|
447
|
-
).toBe('field = -123.45');
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it('handles scientific notation', () => {
|
|
451
|
-
expect(
|
|
452
|
-
translator.translate({
|
|
453
|
-
field: 1e-10,
|
|
454
|
-
}),
|
|
455
|
-
).toBe('field = 0.0000000001');
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
describe('array indexing', () => {
|
|
460
|
-
it('handles positive array indices', () => {
|
|
461
|
-
expect(
|
|
462
|
-
translator.translate({
|
|
463
|
-
'array[0]': 'value',
|
|
464
|
-
'nested.array[2]': 'other',
|
|
465
|
-
}),
|
|
466
|
-
).toBe("(array[0] = 'value' AND nested.array[2] = 'other')");
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it('handles negative array indices', () => {
|
|
470
|
-
expect(
|
|
471
|
-
translator.translate({
|
|
472
|
-
'array[#-1]': 'last',
|
|
473
|
-
'nested.array[#-2]': 'second-to-last',
|
|
474
|
-
}),
|
|
475
|
-
).toBe("(array[#-1] = 'last' AND nested.array[#-2] = 'second-to-last')");
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
it('handles array indices with operators', () => {
|
|
479
|
-
expect(
|
|
480
|
-
translator.translate({
|
|
481
|
-
'array[0]': { $gt: 100 },
|
|
482
|
-
'array[#-1]': { $lt: 200 },
|
|
483
|
-
}),
|
|
484
|
-
).toBe('(array[0] > 100 AND array[#-1] < 200)');
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it('handles array indices in complex queries', () => {
|
|
488
|
-
expect(
|
|
489
|
-
translator.translate({
|
|
490
|
-
$or: [{ 'array[0]': { $in: ['a', 'b'] } }, { 'array[#-1]': { $contains: 'value' } }],
|
|
491
|
-
}),
|
|
492
|
-
).toBe("(array[0] IN ('a', 'b') OR array[#-1] CONTAINS 'value')");
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it('handles multiple array indices', () => {
|
|
496
|
-
expect(
|
|
497
|
-
translator.translate({
|
|
498
|
-
'array[0][1]': 'value',
|
|
499
|
-
'nested[#-1][2]': 'other',
|
|
500
|
-
}),
|
|
501
|
-
).toBe("(array[0][1] = 'value' AND nested[#-1][2] = 'other')");
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
describe('validate operators', () => {
|
|
506
|
-
it('ensure all operator filters are supported', () => {
|
|
507
|
-
const supportedFilters = [
|
|
508
|
-
{ field: { $eq: 'value' } },
|
|
509
|
-
{ field: { $ne: 'value' } },
|
|
510
|
-
{ field: { $gt: 'value' } },
|
|
511
|
-
{ field: { $gte: 'value' } },
|
|
512
|
-
{ field: { $lt: 'value' } },
|
|
513
|
-
{ field: { $lte: 'value' } },
|
|
514
|
-
{ field: { $in: ['value'] } },
|
|
515
|
-
{ field: { $nin: ['value'] } },
|
|
516
|
-
];
|
|
517
|
-
supportedFilters.forEach(filter => {
|
|
518
|
-
expect(() => translator.translate(filter)).not.toThrow();
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('throws error for unsupported operators', () => {
|
|
523
|
-
const unsupportedFilters = [
|
|
524
|
-
{ field: { $invalid: 'value' } },
|
|
525
|
-
{ field: { $elemMatch: { $gt: 5 } } },
|
|
526
|
-
{ field: { $regex: 'pattern', $options: 'i' } },
|
|
527
|
-
];
|
|
528
|
-
|
|
529
|
-
unsupportedFilters.forEach(filter => {
|
|
530
|
-
expect(() => translator.translate(filter)).toThrow(/Unsupported operator/);
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
it('throws error for invalid $not operator', () => {
|
|
535
|
-
expect(() => translator.translate({ field: { $not: true } } as any)).toThrow();
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
it('throws error for regex operators', () => {
|
|
539
|
-
const filter = { field: /pattern/i };
|
|
540
|
-
expect(() => translator.translate(filter)).toThrow();
|
|
541
|
-
});
|
|
542
|
-
it('throws error for non-logical operators at top level', () => {
|
|
543
|
-
const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
|
|
544
|
-
|
|
545
|
-
invalidFilters.forEach(filter => {
|
|
546
|
-
expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
it('allows logical operators at top level', () => {
|
|
551
|
-
const validFilters = [{ $and: [{ field: 'value' }] }, { $or: [{ field: 'value' }] }];
|
|
552
|
-
|
|
553
|
-
validFilters.forEach(filter => {
|
|
554
|
-
expect(() => translator.translate(filter)).not.toThrow();
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
});
|