@mastra/lance 0.2.9 → 0.2.11-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 +19 -6
- package/.turbo/turbo-build.log +0 -4
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -2
- package/src/storage/domains/legacy-evals/index.ts +0 -156
- package/src/storage/domains/memory/index.ts +0 -1000
- package/src/storage/domains/operations/index.ts +0 -489
- package/src/storage/domains/scores/index.ts +0 -243
- package/src/storage/domains/traces/index.ts +0 -212
- package/src/storage/domains/utils.ts +0 -158
- package/src/storage/domains/workflows/index.ts +0 -245
- package/src/storage/index.test.ts +0 -10
- package/src/storage/index.ts +0 -494
- package/src/vector/filter.test.ts +0 -295
- package/src/vector/filter.ts +0 -443
- package/src/vector/index.test.ts +0 -1493
- package/src/vector/index.ts +0 -941
- package/src/vector/types.ts +0 -16
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
package/src/vector/filter.ts
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
-
import type {
|
|
3
|
-
VectorFilter,
|
|
4
|
-
OperatorValueMap,
|
|
5
|
-
LogicalOperatorValueMap,
|
|
6
|
-
BlacklistedRootOperators,
|
|
7
|
-
} from '@mastra/core/vector/filter';
|
|
8
|
-
|
|
9
|
-
type LanceOperatorValueMap = OperatorValueMap & {
|
|
10
|
-
$like: string;
|
|
11
|
-
$notLike: string;
|
|
12
|
-
$contains: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type LanceBlacklisted = BlacklistedRootOperators | '$like' | '$notLike' | '$contains';
|
|
16
|
-
|
|
17
|
-
export type LanceVectorFilter = VectorFilter<
|
|
18
|
-
keyof LanceOperatorValueMap,
|
|
19
|
-
LanceOperatorValueMap,
|
|
20
|
-
LogicalOperatorValueMap,
|
|
21
|
-
LanceBlacklisted
|
|
22
|
-
>;
|
|
23
|
-
|
|
24
|
-
export class LanceFilterTranslator extends BaseFilterTranslator<LanceVectorFilter, string> {
|
|
25
|
-
translate(filter: LanceVectorFilter): string {
|
|
26
|
-
if (!filter || Object.keys(filter).length === 0) {
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check for fields with periods that aren't nested at top level
|
|
31
|
-
if (typeof filter === 'object' && filter !== null) {
|
|
32
|
-
const keys = Object.keys(filter);
|
|
33
|
-
for (const key of keys) {
|
|
34
|
-
if (key.includes('.') && !this.isNormalNestedField(key)) {
|
|
35
|
-
throw new Error(`Field names containing periods (.) are not supported: ${key}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return this.processFilter(filter);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private processFilter(filter: unknown, parentPath = ''): string {
|
|
44
|
-
// Handle null case
|
|
45
|
-
if (filter === null) {
|
|
46
|
-
return `${parentPath} IS NULL`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Handle Date objects at top level
|
|
50
|
-
if (filter instanceof Date) {
|
|
51
|
-
return `${parentPath} = ${this.formatValue(filter)}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Handle top-level operators
|
|
55
|
-
if (typeof filter === 'object' && filter !== null) {
|
|
56
|
-
const obj = filter as Record<string, unknown>;
|
|
57
|
-
const keys = Object.keys(obj);
|
|
58
|
-
|
|
59
|
-
// Handle logical operators at top level
|
|
60
|
-
if (keys.length === 1 && this.isOperator(keys[0]!)) {
|
|
61
|
-
const operator = keys[0]!;
|
|
62
|
-
const operatorValue = obj[operator];
|
|
63
|
-
|
|
64
|
-
if (this.isLogicalOperator(operator)) {
|
|
65
|
-
if (operator === '$and' || operator === '$or') {
|
|
66
|
-
return this.processLogicalOperator(operator, operatorValue as unknown[]);
|
|
67
|
-
}
|
|
68
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.UNSUPPORTED_OPERATOR(operator));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.INVALID_TOP_LEVEL_OPERATOR(operator));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check for fields with periods that aren't nested
|
|
75
|
-
for (const key of keys) {
|
|
76
|
-
if (key.includes('.') && !this.isNormalNestedField(key)) {
|
|
77
|
-
throw new Error(`Field names containing periods (.) are not supported: ${key}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Handle multiple fields (implicit AND)
|
|
82
|
-
if (keys.length > 1) {
|
|
83
|
-
const conditions = keys.map(key => {
|
|
84
|
-
const value = obj[key];
|
|
85
|
-
// Check if key is a nested path or a field
|
|
86
|
-
if (this.isNestedObject(value) && !this.isDateObject(value)) {
|
|
87
|
-
return this.processNestedObject(key, value);
|
|
88
|
-
} else {
|
|
89
|
-
return this.processField(key, value);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
return conditions.join(' AND ');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Handle single field
|
|
96
|
-
if (keys.length === 1) {
|
|
97
|
-
const key = keys[0]!;
|
|
98
|
-
const value = obj[key]!;
|
|
99
|
-
|
|
100
|
-
if (this.isNestedObject(value) && !this.isDateObject(value)) {
|
|
101
|
-
return this.processNestedObject(key!, value);
|
|
102
|
-
} else {
|
|
103
|
-
return this.processField(key!, value);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return '';
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private processLogicalOperator(operator: string, conditions: unknown[]): string {
|
|
112
|
-
if (!Array.isArray(conditions)) {
|
|
113
|
-
throw new Error(`Logical operator ${operator} must have an array value`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (conditions.length === 0) {
|
|
117
|
-
return operator === '$and' ? 'true' : 'false';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const sqlOperator = operator === '$and' ? 'AND' : 'OR';
|
|
121
|
-
|
|
122
|
-
const processedConditions = conditions.map(condition => {
|
|
123
|
-
if (typeof condition !== 'object' || condition === null) {
|
|
124
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.INVALID_LOGICAL_OPERATOR_CONTENT(operator));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Check if condition is a nested logical operator
|
|
128
|
-
const condObj = condition as Record<string, unknown>;
|
|
129
|
-
const keys = Object.keys(condObj);
|
|
130
|
-
|
|
131
|
-
if (keys.length === 1 && this.isOperator(keys[0]!)) {
|
|
132
|
-
if (this.isLogicalOperator(keys[0])) {
|
|
133
|
-
return `(${this.processLogicalOperator(keys[0], condObj[keys[0]] as unknown[])})`;
|
|
134
|
-
} else {
|
|
135
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.UNSUPPORTED_OPERATOR(keys[0]));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Handle multiple fields within a logical condition (implicit AND)
|
|
140
|
-
if (keys.length > 1) {
|
|
141
|
-
return `(${this.processFilter(condition)})`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return this.processFilter(condition);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return processedConditions.join(` ${sqlOperator} `);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private processNestedObject(path: string, value: unknown): string {
|
|
151
|
-
if (typeof value !== 'object' || value === null) {
|
|
152
|
-
throw new Error(`Expected object for nested path ${path}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const obj = value as Record<string, unknown>;
|
|
156
|
-
const keys = Object.keys(obj);
|
|
157
|
-
|
|
158
|
-
// Handle empty object
|
|
159
|
-
if (keys.length === 0) {
|
|
160
|
-
return `${path} = {}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Handle operators on a field
|
|
164
|
-
if (keys.every(k => this.isOperator(k))) {
|
|
165
|
-
return this.processOperators(path, obj);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Process each nested field and join with AND
|
|
169
|
-
const conditions = keys.map(key => {
|
|
170
|
-
const nestedPath = key.includes('.')
|
|
171
|
-
? `${path}.${key}` // Key already contains dots (pre-dotted path)
|
|
172
|
-
: `${path}.${key}`; // Normal nested field
|
|
173
|
-
|
|
174
|
-
if (this.isNestedObject(obj[key]) && !this.isDateObject(obj[key])) {
|
|
175
|
-
return this.processNestedObject(nestedPath, obj[key]);
|
|
176
|
-
} else {
|
|
177
|
-
return this.processField(nestedPath, obj[key]);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return conditions.join(' AND ');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private processField(field: string, value: unknown): string {
|
|
185
|
-
// Check for illegal field names
|
|
186
|
-
if (field.includes('.') && !this.isNormalNestedField(field)) {
|
|
187
|
-
throw new Error(`Field names containing periods (.) are not supported: ${field}`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Escape field name if needed
|
|
191
|
-
const escapedField = this.escapeFieldName(field);
|
|
192
|
-
|
|
193
|
-
// Handle null value
|
|
194
|
-
if (value === null) {
|
|
195
|
-
return `${escapedField} IS NULL`;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Handle Date objects properly
|
|
199
|
-
if (value instanceof Date) {
|
|
200
|
-
return `${escapedField} = ${this.formatValue(value)}`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Handle arrays (convert to IN)
|
|
204
|
-
if (Array.isArray(value)) {
|
|
205
|
-
if (value.length === 0) {
|
|
206
|
-
return 'false'; // Empty array is usually false in SQL
|
|
207
|
-
}
|
|
208
|
-
const normalizedValues = this.normalizeArrayValues(value);
|
|
209
|
-
return `${escapedField} IN (${this.formatArrayValues(normalizedValues)})`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Handle operator objects
|
|
213
|
-
if (this.isOperatorObject(value)) {
|
|
214
|
-
return this.processOperators(field, value as Record<string, unknown>);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Handle basic values (normalize dates and other special values)
|
|
218
|
-
return `${escapedField} = ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private processOperators(field: string, operators: Record<string, unknown>): string {
|
|
222
|
-
const escapedField = this.escapeFieldName(field);
|
|
223
|
-
const operatorKeys = Object.keys(operators);
|
|
224
|
-
|
|
225
|
-
// Check for logical operators at field level
|
|
226
|
-
if (operatorKeys.some(op => this.isLogicalOperator(op))) {
|
|
227
|
-
const logicalOp = operatorKeys.find(op => this.isLogicalOperator(op)) || '';
|
|
228
|
-
throw new Error(`Unsupported operator: ${logicalOp} cannot be used at field level`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Process each operator and join with AND
|
|
232
|
-
return operatorKeys
|
|
233
|
-
.map(op => {
|
|
234
|
-
const value = operators[op];
|
|
235
|
-
|
|
236
|
-
// Check if this is a supported operator
|
|
237
|
-
if (!this.isFieldOperator(op) && !this.isCustomOperator(op)) {
|
|
238
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.UNSUPPORTED_OPERATOR(op));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
switch (op) {
|
|
242
|
-
case '$eq':
|
|
243
|
-
if (value === null) {
|
|
244
|
-
return `${escapedField} IS NULL`;
|
|
245
|
-
}
|
|
246
|
-
return `${escapedField} = ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
247
|
-
case '$ne':
|
|
248
|
-
if (value === null) {
|
|
249
|
-
return `${escapedField} IS NOT NULL`;
|
|
250
|
-
}
|
|
251
|
-
return `${escapedField} != ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
252
|
-
case '$gt':
|
|
253
|
-
return `${escapedField} > ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
254
|
-
case '$gte':
|
|
255
|
-
return `${escapedField} >= ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
256
|
-
case '$lt':
|
|
257
|
-
return `${escapedField} < ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
258
|
-
case '$lte':
|
|
259
|
-
return `${escapedField} <= ${this.formatValue(this.normalizeComparisonValue(value))}`;
|
|
260
|
-
case '$in':
|
|
261
|
-
if (!Array.isArray(value)) {
|
|
262
|
-
throw new Error(`$in operator requires array value for field: ${field}`);
|
|
263
|
-
}
|
|
264
|
-
if (value.length === 0) {
|
|
265
|
-
return 'false'; // Empty IN is false
|
|
266
|
-
}
|
|
267
|
-
const normalizedValues = this.normalizeArrayValues(value);
|
|
268
|
-
return `${escapedField} IN (${this.formatArrayValues(normalizedValues)})`;
|
|
269
|
-
case '$like':
|
|
270
|
-
return `${escapedField} LIKE ${this.formatValue(value)}`;
|
|
271
|
-
case '$notLike':
|
|
272
|
-
return `${escapedField} NOT LIKE ${this.formatValue(value)}`;
|
|
273
|
-
case '$regex':
|
|
274
|
-
return `regexp_match(${escapedField}, ${this.formatValue(value)})`;
|
|
275
|
-
default:
|
|
276
|
-
throw new Error(BaseFilterTranslator.ErrorMessages.UNSUPPORTED_OPERATOR(op));
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
.join(' AND ');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
private formatValue(value: unknown): string {
|
|
283
|
-
if (value === null) {
|
|
284
|
-
return 'NULL';
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (typeof value === 'string') {
|
|
288
|
-
// Escape single quotes in SQL strings by doubling them
|
|
289
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (typeof value === 'number') {
|
|
293
|
-
return value.toString();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (typeof value === 'boolean') {
|
|
297
|
-
return value ? 'true' : 'false';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (value instanceof Date) {
|
|
301
|
-
return `timestamp '${value.toISOString()}'`;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (typeof value === 'object') {
|
|
305
|
-
if (value instanceof Date) {
|
|
306
|
-
return `timestamp '${value.toISOString()}'`;
|
|
307
|
-
}
|
|
308
|
-
return JSON.stringify(value);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return String(value);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private formatArrayValues(array: unknown[]): string {
|
|
315
|
-
return array.map(item => this.formatValue(item)).join(', ');
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
normalizeArrayValues(array: unknown[]): unknown[] {
|
|
319
|
-
return array.map(item => {
|
|
320
|
-
if (item instanceof Date) {
|
|
321
|
-
return item; // Keep Date objects as is to properly format them later
|
|
322
|
-
}
|
|
323
|
-
return this.normalizeComparisonValue(item);
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
normalizeComparisonValue(value: unknown): unknown {
|
|
328
|
-
// Date objects should be preserved as is, not converted to strings
|
|
329
|
-
if (value instanceof Date) {
|
|
330
|
-
return value;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return super.normalizeComparisonValue(value);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private isOperatorObject(value: unknown): boolean {
|
|
337
|
-
if (typeof value !== 'object' || value === null) {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const obj = value as Record<string, unknown>;
|
|
342
|
-
const keys = Object.keys(obj);
|
|
343
|
-
|
|
344
|
-
return keys.length > 0 && keys.some(key => this.isOperator(key));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
private isNestedObject(value: unknown): boolean {
|
|
348
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private isNormalNestedField(field: string): boolean {
|
|
352
|
-
// Check if field is a proper nested field name
|
|
353
|
-
const parts = field.split('.');
|
|
354
|
-
// A valid nested field shouldn't have empty parts or start/end with a dot
|
|
355
|
-
return !field.startsWith('.') && !field.endsWith('.') && parts.every(part => part.trim().length > 0);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
private escapeFieldName(field: string): string {
|
|
359
|
-
// If field contains special characters or is a SQL keyword, escape with backticks
|
|
360
|
-
if (field.includes(' ') || field.includes('-') || /^[A-Z]+$/.test(field) || this.isSqlKeyword(field)) {
|
|
361
|
-
// For nested fields, escape each part
|
|
362
|
-
if (field.includes('.')) {
|
|
363
|
-
return field
|
|
364
|
-
.split('.')
|
|
365
|
-
.map(part => `\`${part}\``)
|
|
366
|
-
.join('.');
|
|
367
|
-
}
|
|
368
|
-
return `\`${field}\``;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return field;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private isSqlKeyword(str: string): boolean {
|
|
375
|
-
// Common SQL keywords that might need escaping
|
|
376
|
-
const sqlKeywords = [
|
|
377
|
-
'SELECT',
|
|
378
|
-
'FROM',
|
|
379
|
-
'WHERE',
|
|
380
|
-
'AND',
|
|
381
|
-
'OR',
|
|
382
|
-
'NOT',
|
|
383
|
-
'INSERT',
|
|
384
|
-
'UPDATE',
|
|
385
|
-
'DELETE',
|
|
386
|
-
'CREATE',
|
|
387
|
-
'ALTER',
|
|
388
|
-
'DROP',
|
|
389
|
-
'TABLE',
|
|
390
|
-
'VIEW',
|
|
391
|
-
'INDEX',
|
|
392
|
-
'JOIN',
|
|
393
|
-
'INNER',
|
|
394
|
-
'OUTER',
|
|
395
|
-
'LEFT',
|
|
396
|
-
'RIGHT',
|
|
397
|
-
'FULL',
|
|
398
|
-
'UNION',
|
|
399
|
-
'ALL',
|
|
400
|
-
'DISTINCT',
|
|
401
|
-
'AS',
|
|
402
|
-
'ON',
|
|
403
|
-
'BETWEEN',
|
|
404
|
-
'LIKE',
|
|
405
|
-
'IN',
|
|
406
|
-
'IS',
|
|
407
|
-
'NULL',
|
|
408
|
-
'TRUE',
|
|
409
|
-
'FALSE',
|
|
410
|
-
'ASC',
|
|
411
|
-
'DESC',
|
|
412
|
-
'GROUP',
|
|
413
|
-
'ORDER',
|
|
414
|
-
'BY',
|
|
415
|
-
'HAVING',
|
|
416
|
-
'LIMIT',
|
|
417
|
-
'OFFSET',
|
|
418
|
-
'CASE',
|
|
419
|
-
'WHEN',
|
|
420
|
-
'THEN',
|
|
421
|
-
'ELSE',
|
|
422
|
-
'END',
|
|
423
|
-
'CAST',
|
|
424
|
-
'CUBE',
|
|
425
|
-
];
|
|
426
|
-
|
|
427
|
-
return sqlKeywords.includes(str.toUpperCase());
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
private isDateObject(value: unknown): boolean {
|
|
431
|
-
return value instanceof Date;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Override getSupportedOperators to add custom operators for LanceDB
|
|
436
|
-
*/
|
|
437
|
-
protected override getSupportedOperators() {
|
|
438
|
-
return {
|
|
439
|
-
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
440
|
-
custom: ['$like', '$notLike', '$regex'],
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
}
|