@mastra/libsql 0.0.4 → 0.0.5-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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +15 -0
- package/dist/_tsup-dts-rollup.d.cts +18 -29
- package/dist/_tsup-dts-rollup.d.ts +18 -29
- package/dist/index.cjs +285 -184
- package/dist/index.js +285 -184
- package/package.json +2 -2
- package/src/storage/index.ts +18 -10
- package/src/vector/index.test.ts +154 -119
- package/src/vector/index.ts +63 -27
- package/src/vector/sql-builder.ts +243 -175
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/libsql",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5-alpha.0",
|
|
4
4
|
"description": "Libsql provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@libsql/client": "^0.15.4",
|
|
24
|
-
"@mastra/core": "^0.9.
|
|
24
|
+
"@mastra/core": "^0.9.5-alpha.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.5",
|
package/src/storage/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
WorkflowRun,
|
|
19
19
|
WorkflowRuns,
|
|
20
20
|
} from '@mastra/core/storage';
|
|
21
|
+
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
21
22
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
22
23
|
|
|
23
24
|
function safelyParseJSON(jsonString: string): any {
|
|
@@ -48,7 +49,9 @@ export class LibSQLStore extends MastraStorage {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
private getCreateTableSQL(tableName: TABLE_NAMES, schema: Record<string, StorageColumn>): string {
|
|
52
|
+
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
51
53
|
const columns = Object.entries(schema).map(([name, col]) => {
|
|
54
|
+
const parsedColumnName = parseSqlIdentifier(name, 'column name');
|
|
52
55
|
let type = col.type.toUpperCase();
|
|
53
56
|
if (type === 'TEXT') type = 'TEXT';
|
|
54
57
|
if (type === 'TIMESTAMP') type = 'TEXT'; // Store timestamps as ISO strings
|
|
@@ -57,19 +60,19 @@ export class LibSQLStore extends MastraStorage {
|
|
|
57
60
|
const nullable = col.nullable ? '' : 'NOT NULL';
|
|
58
61
|
const primaryKey = col.primaryKey ? 'PRIMARY KEY' : '';
|
|
59
62
|
|
|
60
|
-
return `${
|
|
63
|
+
return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
// For workflow_snapshot table, create a composite primary key
|
|
64
67
|
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
65
|
-
const stmnt = `CREATE TABLE IF NOT EXISTS ${
|
|
68
|
+
const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
|
|
66
69
|
${columns.join(',\n')},
|
|
67
70
|
PRIMARY KEY (workflow_name, run_id)
|
|
68
71
|
)`;
|
|
69
72
|
return stmnt;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
return `CREATE TABLE IF NOT EXISTS ${
|
|
75
|
+
return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(', ')})`;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
async createTable({
|
|
@@ -90,8 +93,9 @@ export class LibSQLStore extends MastraStorage {
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
96
|
+
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
93
97
|
try {
|
|
94
|
-
await this.client.execute(`DELETE FROM ${
|
|
98
|
+
await this.client.execute(`DELETE FROM ${parsedTableName}`);
|
|
95
99
|
} catch (e) {
|
|
96
100
|
if (e instanceof Error) {
|
|
97
101
|
this.logger.error(e.message);
|
|
@@ -103,7 +107,8 @@ export class LibSQLStore extends MastraStorage {
|
|
|
103
107
|
sql: string;
|
|
104
108
|
args: InValue[];
|
|
105
109
|
} {
|
|
106
|
-
const
|
|
110
|
+
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
111
|
+
const columns = Object.keys(record).map(col => parseSqlIdentifier(col, 'column name'));
|
|
107
112
|
const values = Object.values(record).map(v => {
|
|
108
113
|
if (typeof v === `undefined`) {
|
|
109
114
|
// returning an undefined value will cause libsql to throw
|
|
@@ -117,7 +122,7 @@ export class LibSQLStore extends MastraStorage {
|
|
|
117
122
|
const placeholders = values.map(() => '?').join(', ');
|
|
118
123
|
|
|
119
124
|
return {
|
|
120
|
-
sql: `INSERT OR REPLACE INTO ${
|
|
125
|
+
sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(', ')}) VALUES (${placeholders})`,
|
|
121
126
|
args: values,
|
|
122
127
|
};
|
|
123
128
|
}
|
|
@@ -149,13 +154,15 @@ export class LibSQLStore extends MastraStorage {
|
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
const parsedTableName = parseSqlIdentifier(tableName, 'table name');
|
|
158
|
+
|
|
159
|
+
const parsedKeys = Object.keys(keys).map(key => parseSqlIdentifier(key, 'column name'));
|
|
160
|
+
|
|
161
|
+
const conditions = parsedKeys.map(key => `${key} = ?`).join(' AND ');
|
|
155
162
|
const values = Object.values(keys);
|
|
156
163
|
|
|
157
164
|
const result = await this.client.execute({
|
|
158
|
-
sql: `SELECT * FROM ${
|
|
165
|
+
sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
|
|
159
166
|
args: values,
|
|
160
167
|
});
|
|
161
168
|
|
|
@@ -506,6 +513,7 @@ export class LibSQLStore extends MastraStorage {
|
|
|
506
513
|
if (toDate) {
|
|
507
514
|
conditions.push('createdAt <= ?');
|
|
508
515
|
}
|
|
516
|
+
|
|
509
517
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
510
518
|
|
|
511
519
|
if (name) {
|
package/src/vector/index.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ describe('LibSQLVector', () => {
|
|
|
15
15
|
|
|
16
16
|
afterAll(async () => {
|
|
17
17
|
// Clean up test tables
|
|
18
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
18
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
// Index Management Tests
|
|
@@ -24,7 +24,7 @@ describe('LibSQLVector', () => {
|
|
|
24
24
|
it('should create a new vector table with specified dimensions', async () => {
|
|
25
25
|
await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
26
26
|
|
|
27
|
-
const stats = await vectorDB.describeIndex(testIndexName);
|
|
27
|
+
const stats = await vectorDB.describeIndex({ indexName: testIndexName });
|
|
28
28
|
expect(stats?.dimension).toBe(3);
|
|
29
29
|
expect(stats?.count).toBe(0);
|
|
30
30
|
});
|
|
@@ -49,7 +49,7 @@ describe('LibSQLVector', () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
afterAll(async () => {
|
|
52
|
-
await vectorDB.deleteIndex(indexName);
|
|
52
|
+
await vectorDB.deleteIndex({ indexName });
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('should list all vector tables', async () => {
|
|
@@ -71,7 +71,7 @@ describe('LibSQLVector', () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
afterAll(async () => {
|
|
74
|
-
await vectorDB.deleteIndex(indexName);
|
|
74
|
+
await vectorDB.deleteIndex({ indexName });
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
it('should return correct index stats', async () => {
|
|
@@ -82,7 +82,7 @@ describe('LibSQLVector', () => {
|
|
|
82
82
|
];
|
|
83
83
|
await vectorDB.upsert({ indexName, vectors });
|
|
84
84
|
|
|
85
|
-
const stats = await vectorDB.describeIndex(indexName);
|
|
85
|
+
const stats = await vectorDB.describeIndex({ indexName });
|
|
86
86
|
expect(stats).toEqual({
|
|
87
87
|
dimension: 3,
|
|
88
88
|
count: 2,
|
|
@@ -91,7 +91,7 @@ describe('LibSQLVector', () => {
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('should throw error for non-existent index', async () => {
|
|
94
|
-
await expect(vectorDB.describeIndex('non_existent')).rejects.toThrow();
|
|
94
|
+
await expect(vectorDB.describeIndex({ indexName: 'non_existent' })).rejects.toThrow();
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
});
|
|
@@ -104,7 +104,7 @@ describe('LibSQLVector', () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
afterEach(async () => {
|
|
107
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
107
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it('should insert new vectors', async () => {
|
|
@@ -115,7 +115,7 @@ describe('LibSQLVector', () => {
|
|
|
115
115
|
const ids = await vectorDB.upsert({ indexName: testIndexName, vectors });
|
|
116
116
|
|
|
117
117
|
expect(ids).toHaveLength(2);
|
|
118
|
-
const stats = await vectorDB.describeIndex(testIndexName);
|
|
118
|
+
const stats = await vectorDB.describeIndex({ indexName: testIndexName });
|
|
119
119
|
expect(stats.count).toBe(2);
|
|
120
120
|
});
|
|
121
121
|
|
|
@@ -165,7 +165,7 @@ describe('LibSQLVector', () => {
|
|
|
165
165
|
expect(ids).toHaveLength(2);
|
|
166
166
|
const id = ids[1];
|
|
167
167
|
|
|
168
|
-
await vectorDB.deleteVector(testIndexName, ids[0]);
|
|
168
|
+
await vectorDB.deleteVector({ indexName: testIndexName, id: ids[0] });
|
|
169
169
|
|
|
170
170
|
const results = await vectorDB.query({ indexName: testIndexName, queryVector: [1, 2, 3] });
|
|
171
171
|
expect(results).toHaveLength(1);
|
|
@@ -181,7 +181,7 @@ describe('LibSQLVector', () => {
|
|
|
181
181
|
vector: [4, 5, 6],
|
|
182
182
|
metadata: { test: 'updated' },
|
|
183
183
|
};
|
|
184
|
-
await vectorDB.updateVector(testIndexName, id, update);
|
|
184
|
+
await vectorDB.updateVector({ indexName: testIndexName, id, update });
|
|
185
185
|
|
|
186
186
|
const results = await vectorDB.query({
|
|
187
187
|
indexName: testIndexName,
|
|
@@ -202,7 +202,7 @@ describe('LibSQLVector', () => {
|
|
|
202
202
|
const update = {
|
|
203
203
|
metadata: { test: 'updated' },
|
|
204
204
|
};
|
|
205
|
-
await vectorDB.updateVector(testIndexName, id, update);
|
|
205
|
+
await vectorDB.updateVector({ indexName: testIndexName, id, update });
|
|
206
206
|
|
|
207
207
|
const results = await vectorDB.query({ indexName: testIndexName, queryVector: [1, 2, 3], topK: 1 });
|
|
208
208
|
expect(results[0]?.id).toBe(id);
|
|
@@ -218,7 +218,7 @@ describe('LibSQLVector', () => {
|
|
|
218
218
|
const update = {
|
|
219
219
|
vector: [4, 5, 6],
|
|
220
220
|
};
|
|
221
|
-
await vectorDB.updateVector(testIndexName, id, update);
|
|
221
|
+
await vectorDB.updateVector({ indexName: testIndexName, id, update });
|
|
222
222
|
|
|
223
223
|
const results = await vectorDB.query({
|
|
224
224
|
indexName: testIndexName,
|
|
@@ -236,7 +236,9 @@ describe('LibSQLVector', () => {
|
|
|
236
236
|
const metadata = [{ test: 'initial' }];
|
|
237
237
|
const [id] = await vectorDB.upsert({ indexName: testIndexName, vectors, metadata });
|
|
238
238
|
|
|
239
|
-
await expect(vectorDB.updateVector(testIndexName, id, {})).rejects.toThrow(
|
|
239
|
+
await expect(vectorDB.updateVector({ indexName: testIndexName, id, update: {} })).rejects.toThrow(
|
|
240
|
+
'No updates provided',
|
|
241
|
+
);
|
|
240
242
|
});
|
|
241
243
|
});
|
|
242
244
|
|
|
@@ -258,7 +260,7 @@ describe('LibSQLVector', () => {
|
|
|
258
260
|
});
|
|
259
261
|
|
|
260
262
|
afterEach(async () => {
|
|
261
|
-
await vectorDB.deleteIndex(indexName);
|
|
263
|
+
await vectorDB.deleteIndex({ indexName });
|
|
262
264
|
});
|
|
263
265
|
|
|
264
266
|
it('should return closest vectors', async () => {
|
|
@@ -345,7 +347,7 @@ describe('LibSQLVector', () => {
|
|
|
345
347
|
});
|
|
346
348
|
|
|
347
349
|
afterEach(async () => {
|
|
348
|
-
await vectorDB.deleteIndex(indexName);
|
|
350
|
+
await vectorDB.deleteIndex({ indexName });
|
|
349
351
|
});
|
|
350
352
|
|
|
351
353
|
// Numeric Comparison Tests
|
|
@@ -357,7 +359,7 @@ describe('LibSQLVector', () => {
|
|
|
357
359
|
const results = await vectorDB.query({
|
|
358
360
|
indexName,
|
|
359
361
|
queryVector: [1, 0, 0],
|
|
360
|
-
filter: { numericString: { $gt: '100' } },
|
|
362
|
+
filter: { numericString: { $gt: '100' } },
|
|
361
363
|
});
|
|
362
364
|
expect(results.length).toBeGreaterThan(0);
|
|
363
365
|
expect(results[0]?.metadata?.numericString).toBe('123');
|
|
@@ -437,7 +439,7 @@ describe('LibSQLVector', () => {
|
|
|
437
439
|
|
|
438
440
|
// Array Operator Tests
|
|
439
441
|
describe('Array Operators', () => {
|
|
440
|
-
it('should filter with $in operator', async () => {
|
|
442
|
+
it('should filter with $in operator for scalar field', async () => {
|
|
441
443
|
const results = await vectorDB.query({
|
|
442
444
|
indexName,
|
|
443
445
|
queryVector: [1, 0, 0],
|
|
@@ -449,7 +451,25 @@ describe('LibSQLVector', () => {
|
|
|
449
451
|
});
|
|
450
452
|
});
|
|
451
453
|
|
|
452
|
-
it('should filter with $
|
|
454
|
+
it('should filter with $in operator for array field', async () => {
|
|
455
|
+
// Insert a record with tags as array
|
|
456
|
+
await vectorDB.upsert({
|
|
457
|
+
indexName,
|
|
458
|
+
vectors: [[2, 0.2, 0]],
|
|
459
|
+
metadata: [{ tags: ['featured', 'sale', 'new'] }],
|
|
460
|
+
});
|
|
461
|
+
const results = await vectorDB.query({
|
|
462
|
+
indexName,
|
|
463
|
+
queryVector: [1, 0, 0],
|
|
464
|
+
filter: { tags: { $in: ['sale', 'clearance'] } },
|
|
465
|
+
});
|
|
466
|
+
expect(results.length).toBeGreaterThan(0);
|
|
467
|
+
results.forEach(result => {
|
|
468
|
+
expect(result.metadata?.tags.some((tag: string) => ['sale', 'clearance'].includes(tag))).toBe(true);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should filter with $nin operator for scalar field', async () => {
|
|
453
473
|
const results = await vectorDB.query({
|
|
454
474
|
indexName,
|
|
455
475
|
queryVector: [1, 0, 0],
|
|
@@ -461,6 +481,24 @@ describe('LibSQLVector', () => {
|
|
|
461
481
|
});
|
|
462
482
|
});
|
|
463
483
|
|
|
484
|
+
it('should filter with $nin operator for array field', async () => {
|
|
485
|
+
// Insert a record with tags as array
|
|
486
|
+
await vectorDB.upsert({
|
|
487
|
+
indexName,
|
|
488
|
+
vectors: [[2, 0.3, 0]],
|
|
489
|
+
metadata: [{ tags: ['clearance', 'used'] }],
|
|
490
|
+
});
|
|
491
|
+
const results = await vectorDB.query({
|
|
492
|
+
indexName,
|
|
493
|
+
queryVector: [1, 0, 0],
|
|
494
|
+
filter: { tags: { $nin: ['new', 'sale'] } },
|
|
495
|
+
});
|
|
496
|
+
expect(results.length).toBeGreaterThan(0);
|
|
497
|
+
results.forEach(result => {
|
|
498
|
+
expect(result.metadata?.tags.every((tag: string) => !['new', 'sale'].includes(tag))).toBe(true);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
464
502
|
it('should handle empty arrays in in/nin operators', async () => {
|
|
465
503
|
// Should return no results for empty IN
|
|
466
504
|
const resultsIn = await vectorDB.query({
|
|
@@ -491,17 +529,57 @@ describe('LibSQLVector', () => {
|
|
|
491
529
|
});
|
|
492
530
|
});
|
|
493
531
|
|
|
532
|
+
it('should filter with $contains operator for string substring', async () => {
|
|
533
|
+
const results = await vectorDB.query({
|
|
534
|
+
indexName,
|
|
535
|
+
queryVector: [1, 0, 0],
|
|
536
|
+
filter: { category: { $contains: 'lectro' } },
|
|
537
|
+
});
|
|
538
|
+
expect(results.length).toBeGreaterThan(0);
|
|
539
|
+
results.forEach(result => {
|
|
540
|
+
expect(result.metadata?.category).toContain('lectro');
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should not match deep object containment with $contains', async () => {
|
|
545
|
+
// Insert a record with a nested object
|
|
546
|
+
await vectorDB.upsert({
|
|
547
|
+
indexName,
|
|
548
|
+
vectors: [[1, 0.1, 0]],
|
|
549
|
+
metadata: [{ details: { color: 'red', size: 'large' }, category: 'clothing' }],
|
|
550
|
+
});
|
|
551
|
+
// $contains does NOT support deep object containment in Postgres
|
|
552
|
+
const results = await vectorDB.query({
|
|
553
|
+
indexName,
|
|
554
|
+
queryVector: [1, 0.1, 0],
|
|
555
|
+
filter: { details: { $contains: { color: 'red' } } },
|
|
556
|
+
});
|
|
557
|
+
expect(results.length).toBe(0);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('should fallback to direct equality for non-array, non-string', async () => {
|
|
561
|
+
// Insert a record with a numeric field
|
|
562
|
+
await vectorDB.upsert({
|
|
563
|
+
indexName,
|
|
564
|
+
vectors: [[1, 0.2, 0]],
|
|
565
|
+
metadata: [{ price: 123 }],
|
|
566
|
+
});
|
|
567
|
+
const results = await vectorDB.query({
|
|
568
|
+
indexName,
|
|
569
|
+
queryVector: [1, 0, 0],
|
|
570
|
+
filter: { price: { $contains: 123 } },
|
|
571
|
+
});
|
|
572
|
+
expect(results.length).toBeGreaterThan(0);
|
|
573
|
+
results.forEach(result => {
|
|
574
|
+
expect(result.metadata?.price).toBe(123);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
494
578
|
it('should filter with $elemMatch operator', async () => {
|
|
495
579
|
const results = await vectorDB.query({
|
|
496
580
|
indexName,
|
|
497
581
|
queryVector: [1, 0, 0],
|
|
498
|
-
filter: {
|
|
499
|
-
tags: {
|
|
500
|
-
$elemMatch: {
|
|
501
|
-
$in: ['new', 'premium'],
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
},
|
|
582
|
+
filter: { tags: { $elemMatch: { $in: ['new', 'premium'] } } },
|
|
505
583
|
});
|
|
506
584
|
expect(results.length).toBeGreaterThan(0);
|
|
507
585
|
results.forEach(result => {
|
|
@@ -513,13 +591,7 @@ describe('LibSQLVector', () => {
|
|
|
513
591
|
const results = await vectorDB.query({
|
|
514
592
|
indexName,
|
|
515
593
|
queryVector: [1, 0, 0],
|
|
516
|
-
filter: {
|
|
517
|
-
tags: {
|
|
518
|
-
$elemMatch: {
|
|
519
|
-
$eq: 'sale',
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
},
|
|
594
|
+
filter: { tags: { $elemMatch: { $eq: 'sale' } } },
|
|
523
595
|
});
|
|
524
596
|
expect(results).toHaveLength(1);
|
|
525
597
|
expect(results[0]?.metadata?.tags).toContain('sale');
|
|
@@ -529,14 +601,7 @@ describe('LibSQLVector', () => {
|
|
|
529
601
|
const results = await vectorDB.query({
|
|
530
602
|
indexName,
|
|
531
603
|
queryVector: [1, 0, 0],
|
|
532
|
-
filter: {
|
|
533
|
-
ratings: {
|
|
534
|
-
$elemMatch: {
|
|
535
|
-
$gt: 4,
|
|
536
|
-
$lt: 4.5,
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
},
|
|
604
|
+
filter: { ratings: { $elemMatch: { $gt: 4, $lt: 4.5 } } },
|
|
540
605
|
});
|
|
541
606
|
expect(results.length).toBeGreaterThan(0);
|
|
542
607
|
results.forEach(result => {
|
|
@@ -549,14 +614,7 @@ describe('LibSQLVector', () => {
|
|
|
549
614
|
const results = await vectorDB.query({
|
|
550
615
|
indexName,
|
|
551
616
|
queryVector: [1, 0, 0],
|
|
552
|
-
filter: {
|
|
553
|
-
stock: {
|
|
554
|
-
$elemMatch: {
|
|
555
|
-
location: 'A',
|
|
556
|
-
count: { $gt: 20 },
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
},
|
|
617
|
+
filter: { stock: { $elemMatch: { location: 'A', count: { $gt: 20 } } } },
|
|
560
618
|
});
|
|
561
619
|
expect(results.length).toBeGreaterThan(0);
|
|
562
620
|
results.forEach(result => {
|
|
@@ -569,13 +627,7 @@ describe('LibSQLVector', () => {
|
|
|
569
627
|
const results = await vectorDB.query({
|
|
570
628
|
indexName,
|
|
571
629
|
queryVector: [1, 0, 0],
|
|
572
|
-
filter: {
|
|
573
|
-
reviews: {
|
|
574
|
-
$elemMatch: {
|
|
575
|
-
score: { $gt: 4 },
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
},
|
|
630
|
+
filter: { reviews: { $elemMatch: { score: { $gt: 4 } } } },
|
|
579
631
|
});
|
|
580
632
|
expect(results.length).toBeGreaterThan(0);
|
|
581
633
|
results.forEach(result => {
|
|
@@ -587,14 +639,7 @@ describe('LibSQLVector', () => {
|
|
|
587
639
|
const results = await vectorDB.query({
|
|
588
640
|
indexName,
|
|
589
641
|
queryVector: [1, 0, 0],
|
|
590
|
-
filter: {
|
|
591
|
-
reviews: {
|
|
592
|
-
$elemMatch: {
|
|
593
|
-
score: { $gte: 4 },
|
|
594
|
-
verified: true,
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
|
-
},
|
|
642
|
+
filter: { reviews: { $elemMatch: { score: { $gte: 4 }, verified: true } } },
|
|
598
643
|
});
|
|
599
644
|
expect(results.length).toBeGreaterThan(0);
|
|
600
645
|
results.forEach(result => {
|
|
@@ -606,13 +651,7 @@ describe('LibSQLVector', () => {
|
|
|
606
651
|
const results = await vectorDB.query({
|
|
607
652
|
indexName,
|
|
608
653
|
queryVector: [1, 0, 0],
|
|
609
|
-
filter: {
|
|
610
|
-
reviews: {
|
|
611
|
-
$elemMatch: {
|
|
612
|
-
user: 'alice',
|
|
613
|
-
},
|
|
614
|
-
},
|
|
615
|
-
},
|
|
654
|
+
filter: { reviews: { $elemMatch: { user: 'alice' } } },
|
|
616
655
|
});
|
|
617
656
|
expect(results).toHaveLength(1);
|
|
618
657
|
expect(results[0].metadata?.reviews.some(r => r.user === 'alice')).toBe(true);
|
|
@@ -622,13 +661,7 @@ describe('LibSQLVector', () => {
|
|
|
622
661
|
const results = await vectorDB.query({
|
|
623
662
|
indexName,
|
|
624
663
|
queryVector: [1, 0, 0],
|
|
625
|
-
filter: {
|
|
626
|
-
reviews: {
|
|
627
|
-
$elemMatch: {
|
|
628
|
-
score: 10, // No review has score 10
|
|
629
|
-
},
|
|
630
|
-
},
|
|
631
|
-
},
|
|
664
|
+
filter: { reviews: { $elemMatch: { score: 10 } } },
|
|
632
665
|
});
|
|
633
666
|
expect(results).toHaveLength(0);
|
|
634
667
|
});
|
|
@@ -669,11 +702,7 @@ describe('LibSQLVector', () => {
|
|
|
669
702
|
|
|
670
703
|
it('should handle non-array field $all', async () => {
|
|
671
704
|
// First insert a record with non-array field
|
|
672
|
-
await vectorDB.upsert({
|
|
673
|
-
indexName,
|
|
674
|
-
vectors: [[1, 0.1, 0]],
|
|
675
|
-
metadata: [{ tags: 'not-an-array' }],
|
|
676
|
-
});
|
|
705
|
+
await vectorDB.upsert({ indexName, vectors: [[1, 0.1, 0]], metadata: [{ tags: 'not-an-array' }] });
|
|
677
706
|
|
|
678
707
|
const results = await vectorDB.query({
|
|
679
708
|
indexName,
|
|
@@ -696,29 +725,29 @@ describe('LibSQLVector', () => {
|
|
|
696
725
|
});
|
|
697
726
|
});
|
|
698
727
|
|
|
699
|
-
it('should filter with $
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
});
|
|
728
|
+
// it('should filter with $objectContains operator for nested objects', async () => {
|
|
729
|
+
// // First insert a record with nested object
|
|
730
|
+
// await vectorDB.upsert({
|
|
731
|
+
// indexName,
|
|
732
|
+
// vectors: [[1, 0.1, 0]],
|
|
733
|
+
// metadata: [
|
|
734
|
+
// {
|
|
735
|
+
// details: { color: 'red', size: 'large' },
|
|
736
|
+
// category: 'clothing',
|
|
737
|
+
// },
|
|
738
|
+
// ],
|
|
739
|
+
// });
|
|
740
|
+
|
|
741
|
+
// const results = await vectorDB.query({
|
|
742
|
+
// indexName,
|
|
743
|
+
// queryVector: [1, 0.1, 0],
|
|
744
|
+
// filter: { details: { $objectContains: { color: 'red' } } },
|
|
745
|
+
// });
|
|
746
|
+
// expect(results.length).toBeGreaterThan(0);
|
|
747
|
+
// results.forEach(result => {
|
|
748
|
+
// expect(result.metadata?.details.color).toBe('red');
|
|
749
|
+
// });
|
|
750
|
+
// });
|
|
722
751
|
|
|
723
752
|
// String Pattern Tests
|
|
724
753
|
it('should handle exact string matches', async () => {
|
|
@@ -758,11 +787,7 @@ describe('LibSQLVector', () => {
|
|
|
758
787
|
});
|
|
759
788
|
|
|
760
789
|
it('should handle $size with nested arrays', async () => {
|
|
761
|
-
await vectorDB.upsert({
|
|
762
|
-
indexName,
|
|
763
|
-
vectors: [[1, 0.1, 0]],
|
|
764
|
-
metadata: [{ nested: { array: [1, 2, 3, 4] } }],
|
|
765
|
-
});
|
|
790
|
+
await vectorDB.upsert({ indexName, vectors: [[1, 0.1, 0]], metadata: [{ nested: { array: [1, 2, 3, 4] } }] });
|
|
766
791
|
const results = await vectorDB.query({
|
|
767
792
|
indexName,
|
|
768
793
|
queryVector: [1, 0, 0],
|
|
@@ -900,8 +925,12 @@ describe('LibSQLVector', () => {
|
|
|
900
925
|
filter: {
|
|
901
926
|
$not: {
|
|
902
927
|
$or: [
|
|
903
|
-
{
|
|
904
|
-
|
|
928
|
+
{
|
|
929
|
+
$and: [{ category: 'electronics' }, { price: { $gt: 90 } }],
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
$and: [{ category: 'books' }, { price: { $lt: 30 } }],
|
|
933
|
+
},
|
|
905
934
|
],
|
|
906
935
|
},
|
|
907
936
|
},
|
|
@@ -1306,8 +1335,14 @@ describe('LibSQLVector', () => {
|
|
|
1306
1335
|
queryVector: [1, 0, 0],
|
|
1307
1336
|
filter: {
|
|
1308
1337
|
$and: [
|
|
1309
|
-
{
|
|
1310
|
-
|
|
1338
|
+
{
|
|
1339
|
+
$or: [{ category: 'electronics' }, { $and: [{ category: 'books' }, { price: { $lt: 30 } }] }],
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
$not: {
|
|
1343
|
+
$or: [{ active: false }, { price: { $gt: 100 } }],
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1311
1346
|
],
|
|
1312
1347
|
},
|
|
1313
1348
|
});
|
|
@@ -1609,7 +1644,7 @@ describe('LibSQLVector', () => {
|
|
|
1609
1644
|
});
|
|
1610
1645
|
|
|
1611
1646
|
afterAll(async () => {
|
|
1612
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
1647
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
1613
1648
|
});
|
|
1614
1649
|
it('should handle non-existent index queries', async () => {
|
|
1615
1650
|
await expect(vectorDB.query({ indexName: 'non-existent-index', queryVector: [1, 2, 3] })).rejects.toThrow();
|
|
@@ -1641,7 +1676,7 @@ describe('LibSQLVector', () => {
|
|
|
1641
1676
|
).resolves.not.toThrow();
|
|
1642
1677
|
|
|
1643
1678
|
// Cleanup
|
|
1644
|
-
await vectorDB.deleteIndex(duplicateIndexName);
|
|
1679
|
+
await vectorDB.deleteIndex({ indexName: duplicateIndexName });
|
|
1645
1680
|
});
|
|
1646
1681
|
});
|
|
1647
1682
|
|
|
@@ -1657,8 +1692,8 @@ describe('LibSQLVector', () => {
|
|
|
1657
1692
|
});
|
|
1658
1693
|
|
|
1659
1694
|
afterAll(async () => {
|
|
1660
|
-
await vectorDB.deleteIndex(indexName);
|
|
1661
|
-
await vectorDB.deleteIndex(indexName2);
|
|
1695
|
+
await vectorDB.deleteIndex({ indexName });
|
|
1696
|
+
await vectorDB.deleteIndex({ indexName: indexName2 });
|
|
1662
1697
|
});
|
|
1663
1698
|
|
|
1664
1699
|
beforeEach(async () => {
|
|
@@ -1667,7 +1702,7 @@ describe('LibSQLVector', () => {
|
|
|
1667
1702
|
|
|
1668
1703
|
afterEach(async () => {
|
|
1669
1704
|
warnSpy.mockRestore();
|
|
1670
|
-
await vectorDB.deleteIndex(indexName2);
|
|
1705
|
+
await vectorDB.deleteIndex({ indexName: indexName2 });
|
|
1671
1706
|
});
|
|
1672
1707
|
|
|
1673
1708
|
it('should show deprecation warning when using individual args for createIndex', async () => {
|