@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.
@@ -1,941 +0,0 @@
1
- import { connect, Index } from '@lancedb/lancedb';
2
- import type { Connection, ConnectionOptions, CreateTableOptions, Table, TableLike } from '@lancedb/lancedb';
3
-
4
- import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
5
- import type {
6
- CreateIndexParams,
7
- DeleteIndexParams,
8
- DeleteVectorParams,
9
- DescribeIndexParams,
10
- IndexStats,
11
- QueryResult,
12
- QueryVectorParams,
13
- UpdateVectorParams,
14
- UpsertVectorParams,
15
- } from '@mastra/core/vector';
16
-
17
- import { MastraVector } from '@mastra/core/vector';
18
- import type { LanceVectorFilter } from './filter';
19
- import { LanceFilterTranslator } from './filter';
20
- import type { IndexConfig } from './types';
21
-
22
- interface LanceCreateIndexParams extends CreateIndexParams {
23
- indexConfig?: LanceIndexConfig;
24
- tableName?: string;
25
- }
26
-
27
- interface LanceIndexConfig extends IndexConfig {
28
- numPartitions?: number;
29
- numSubVectors?: number;
30
- }
31
-
32
- interface LanceUpsertVectorParams extends UpsertVectorParams {
33
- tableName: string;
34
- }
35
-
36
- interface LanceQueryVectorParams extends QueryVectorParams<LanceVectorFilter> {
37
- tableName: string;
38
- columns?: string[];
39
- includeAllColumns?: boolean;
40
- }
41
-
42
- export class LanceVectorStore extends MastraVector<LanceVectorFilter> {
43
- private lanceClient!: Connection;
44
-
45
- /**
46
- * Creates a new instance of LanceVectorStore
47
- * @param uri The URI to connect to LanceDB
48
- * @param options connection options
49
- *
50
- * Usage:
51
- *
52
- * Connect to a local database
53
- * ```ts
54
- * const store = await LanceVectorStore.create('/path/to/db');
55
- * ```
56
- *
57
- * Connect to a LanceDB cloud database
58
- * ```ts
59
- * const store = await LanceVectorStore.create('db://host:port');
60
- * ```
61
- *
62
- * Connect to a cloud database
63
- * ```ts
64
- * const store = await LanceVectorStore.create('s3://bucket/db', { storageOptions: { timeout: '60s' } });
65
- * ```
66
- */
67
- public static async create(uri: string, options?: ConnectionOptions): Promise<LanceVectorStore> {
68
- const instance = new LanceVectorStore();
69
- try {
70
- instance.lanceClient = await connect(uri, options);
71
- return instance;
72
- } catch (e) {
73
- throw new MastraError(
74
- {
75
- id: 'STORAGE_LANCE_VECTOR_CONNECT_FAILED',
76
- domain: ErrorDomain.STORAGE,
77
- category: ErrorCategory.THIRD_PARTY,
78
- details: { uri },
79
- },
80
- e,
81
- );
82
- }
83
- }
84
-
85
- /**
86
- * @internal
87
- * Private constructor to enforce using the create factory method
88
- */
89
- private constructor() {
90
- super();
91
- }
92
-
93
- close() {
94
- if (this.lanceClient) {
95
- this.lanceClient.close();
96
- }
97
- }
98
-
99
- async query({
100
- tableName,
101
- queryVector,
102
- filter,
103
- includeVector = false,
104
- topK = 10,
105
- columns = [],
106
- includeAllColumns = false,
107
- }: LanceQueryVectorParams): Promise<QueryResult[]> {
108
- try {
109
- if (!this.lanceClient) {
110
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
111
- }
112
-
113
- if (!tableName) {
114
- throw new Error('tableName is required');
115
- }
116
-
117
- if (!queryVector) {
118
- throw new Error('queryVector is required');
119
- }
120
- } catch (error) {
121
- throw new MastraError(
122
- {
123
- id: 'STORAGE_LANCE_VECTOR_QUERY_FAILED_INVALID_ARGS',
124
- domain: ErrorDomain.STORAGE,
125
- category: ErrorCategory.USER,
126
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
127
- details: { tableName },
128
- },
129
- error,
130
- );
131
- }
132
-
133
- try {
134
- // Open the table
135
- const table = await this.lanceClient.openTable(tableName);
136
-
137
- // Prepare the list of columns to select
138
- const selectColumns = [...columns];
139
- if (!selectColumns.includes('id')) {
140
- selectColumns.push('id');
141
- }
142
-
143
- // Create the query builder
144
- let query = table.search(queryVector);
145
-
146
- // Add filter if provided
147
- if (filter && Object.keys(filter).length > 0) {
148
- const whereClause = this.filterTranslator(filter);
149
- this.logger.debug(`Where clause generated: ${whereClause}`);
150
- query = query.where(whereClause);
151
- }
152
-
153
- // Apply column selection and limit
154
- if (!includeAllColumns && selectColumns.length > 0) {
155
- query = query.select(selectColumns);
156
- }
157
- query = query.limit(topK);
158
-
159
- // Execute the query
160
- const results = await query.toArray();
161
-
162
- return results.map(result => {
163
- // Collect all metadata_ prefixed fields
164
- const flatMetadata: Record<string, any> = {};
165
-
166
- // Get all keys from the result object
167
- Object.keys(result).forEach(key => {
168
- // Skip reserved keys (id, score, and the vector column)
169
- if (key !== 'id' && key !== 'score' && key !== 'vector' && key !== '_distance') {
170
- if (key.startsWith('metadata_')) {
171
- // Remove the prefix and add to flat metadata
172
- const metadataKey = key.substring('metadata_'.length);
173
- flatMetadata[metadataKey] = result[key];
174
- }
175
- }
176
- });
177
-
178
- // Reconstruct nested metadata object
179
- const metadata = this.unflattenObject(flatMetadata);
180
-
181
- return {
182
- id: String(result.id || ''),
183
- metadata,
184
- vector:
185
- includeVector && result.vector
186
- ? Array.isArray(result.vector)
187
- ? result.vector
188
- : Array.from(result.vector as any[])
189
- : undefined,
190
- document: result.document,
191
- score: result._distance,
192
- };
193
- });
194
- } catch (error: any) {
195
- throw new MastraError(
196
- {
197
- id: 'STORAGE_LANCE_VECTOR_QUERY_FAILED',
198
- domain: ErrorDomain.STORAGE,
199
- category: ErrorCategory.THIRD_PARTY,
200
- details: { tableName, includeVector, columnsCount: columns?.length, includeAllColumns },
201
- },
202
- error,
203
- );
204
- }
205
- }
206
-
207
- private filterTranslator(filter: LanceVectorFilter): string {
208
- // Add metadata_ prefix to filter keys if they don't already have it
209
- const processFilterKeys = (filterObj: Record<string, any>): Record<string, any> => {
210
- const result: Record<string, any> = {};
211
-
212
- Object.entries(filterObj).forEach(([key, value]) => {
213
- // Don't add prefix to logical operators
214
- if (key === '$or' || key === '$and' || key === '$not' || key === '$in') {
215
- // For logical operators, process their array contents
216
- if (Array.isArray(value)) {
217
- result[key] = value.map(item =>
218
- typeof item === 'object' && item !== null ? processFilterKeys(item as Record<string, any>) : item,
219
- );
220
- } else {
221
- result[key] = value;
222
- }
223
- }
224
- // Don't add prefix if it already has metadata_ prefix
225
- else if (key.startsWith('metadata_')) {
226
- result[key] = value;
227
- }
228
- // Add metadata_ prefix to regular field keys
229
- else {
230
- // Convert dot notation to underscore notation for nested fields
231
- if (key.includes('.')) {
232
- const convertedKey = `metadata_${key.replace(/\./g, '_')}`;
233
- result[convertedKey] = value;
234
- } else {
235
- result[`metadata_${key}`] = value;
236
- }
237
- }
238
- });
239
-
240
- return result;
241
- };
242
-
243
- const prefixedFilter = filter && typeof filter === 'object' ? processFilterKeys(filter as Record<string, any>) : {};
244
-
245
- const translator = new LanceFilterTranslator();
246
- return translator.translate(prefixedFilter);
247
- }
248
-
249
- async upsert({ tableName, vectors, metadata = [], ids = [] }: LanceUpsertVectorParams): Promise<string[]> {
250
- try {
251
- if (!this.lanceClient) {
252
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
253
- }
254
-
255
- if (!tableName) {
256
- throw new Error('tableName is required');
257
- }
258
-
259
- if (!vectors || !Array.isArray(vectors) || vectors.length === 0) {
260
- throw new Error('vectors array is required and must not be empty');
261
- }
262
- } catch (error) {
263
- throw new MastraError(
264
- {
265
- id: 'STORAGE_LANCE_VECTOR_UPSERT_FAILED_INVALID_ARGS',
266
- domain: ErrorDomain.STORAGE,
267
- category: ErrorCategory.USER,
268
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
269
- details: { tableName },
270
- },
271
- error,
272
- );
273
- }
274
-
275
- try {
276
- const tables = await this.lanceClient.tableNames();
277
- if (!tables.includes(tableName)) {
278
- throw new Error(`Table ${tableName} does not exist`);
279
- }
280
-
281
- const table = await this.lanceClient.openTable(tableName);
282
-
283
- // Generate IDs if not provided
284
- const vectorIds = ids.length === vectors.length ? ids : vectors.map((_, i) => ids[i] || crypto.randomUUID());
285
-
286
- // Create data with metadata fields expanded at the top level
287
- const data = vectors.map((vector, i) => {
288
- const id = String(vectorIds[i]);
289
- const metadataItem = metadata[i] || {};
290
-
291
- // Create the base object with id and vector
292
- const rowData: Record<string, any> = {
293
- id,
294
- vector: vector,
295
- };
296
-
297
- // Flatten the metadata object and prefix all keys with 'metadata_'
298
- if (Object.keys(metadataItem).length > 0) {
299
- const flattenedMetadata = this.flattenObject(metadataItem, 'metadata');
300
- // Add all flattened metadata properties to the row data object
301
- Object.entries(flattenedMetadata).forEach(([key, value]) => {
302
- rowData[key] = value;
303
- });
304
- }
305
-
306
- return rowData;
307
- });
308
-
309
- await table.add(data, { mode: 'overwrite' });
310
-
311
- return vectorIds;
312
- } catch (error: any) {
313
- throw new MastraError(
314
- {
315
- id: 'STORAGE_LANCE_VECTOR_UPSERT_FAILED',
316
- domain: ErrorDomain.STORAGE,
317
- category: ErrorCategory.THIRD_PARTY,
318
- details: { tableName, vectorCount: vectors.length, metadataCount: metadata.length, idsCount: ids.length },
319
- },
320
- error,
321
- );
322
- }
323
- }
324
-
325
- /**
326
- * Flattens a nested object, creating new keys with underscores for nested properties.
327
- * Example: { metadata: { text: 'test' } } → { metadata_text: 'test' }
328
- */
329
- private flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {
330
- return Object.keys(obj).reduce((acc: Record<string, unknown>, k: string) => {
331
- const pre = prefix.length ? `${prefix}_` : '';
332
- if (typeof obj[k] === 'object' && obj[k] !== null && !Array.isArray(obj[k])) {
333
- Object.assign(acc, this.flattenObject(obj[k] as Record<string, unknown>, pre + k));
334
- } else {
335
- acc[pre + k] = obj[k];
336
- }
337
- return acc;
338
- }, {});
339
- }
340
-
341
- async createTable(
342
- tableName: string,
343
- data: Record<string, unknown>[] | TableLike,
344
- options?: Partial<CreateTableOptions>,
345
- ): Promise<Table> {
346
- if (!this.lanceClient) {
347
- throw new MastraError({
348
- id: 'STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED_INVALID_ARGS',
349
- domain: ErrorDomain.STORAGE,
350
- category: ErrorCategory.USER,
351
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
352
- details: { tableName },
353
- });
354
- }
355
-
356
- // Flatten nested objects if data is an array of records
357
- if (Array.isArray(data)) {
358
- data = data.map(record => this.flattenObject(record));
359
- }
360
-
361
- try {
362
- return await this.lanceClient.createTable(tableName, data, options);
363
- } catch (error: any) {
364
- throw new MastraError(
365
- {
366
- id: 'STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED',
367
- domain: ErrorDomain.STORAGE,
368
- category: ErrorCategory.THIRD_PARTY,
369
- details: { tableName },
370
- },
371
- error,
372
- );
373
- }
374
- }
375
-
376
- async listTables(): Promise<string[]> {
377
- if (!this.lanceClient) {
378
- throw new MastraError({
379
- id: 'STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED_INVALID_ARGS',
380
- domain: ErrorDomain.STORAGE,
381
- category: ErrorCategory.USER,
382
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
383
- details: { methodName: 'listTables' },
384
- });
385
- }
386
- try {
387
- return await this.lanceClient.tableNames();
388
- } catch (error) {
389
- throw new MastraError(
390
- {
391
- id: 'STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED',
392
- domain: ErrorDomain.STORAGE,
393
- category: ErrorCategory.THIRD_PARTY,
394
- },
395
- error,
396
- );
397
- }
398
- }
399
-
400
- async getTableSchema(tableName: string): Promise<any> {
401
- if (!this.lanceClient) {
402
- throw new MastraError({
403
- id: 'STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED_INVALID_ARGS',
404
- domain: ErrorDomain.STORAGE,
405
- category: ErrorCategory.USER,
406
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
407
- details: { tableName },
408
- });
409
- }
410
-
411
- try {
412
- const table = await this.lanceClient.openTable(tableName);
413
- return await table.schema();
414
- } catch (error) {
415
- throw new MastraError(
416
- {
417
- id: 'STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED',
418
- domain: ErrorDomain.STORAGE,
419
- category: ErrorCategory.THIRD_PARTY,
420
- details: { tableName },
421
- },
422
- error,
423
- );
424
- }
425
- }
426
-
427
- /**
428
- * indexName is actually a column name in a table in lanceDB
429
- */
430
- async createIndex({
431
- tableName,
432
- indexName,
433
- dimension,
434
- metric = 'cosine',
435
- indexConfig = {},
436
- }: LanceCreateIndexParams): Promise<void> {
437
- try {
438
- if (!this.lanceClient) {
439
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
440
- }
441
-
442
- if (!tableName) {
443
- throw new Error('tableName is required');
444
- }
445
-
446
- if (!indexName) {
447
- throw new Error('indexName is required');
448
- }
449
-
450
- if (typeof dimension !== 'number' || dimension <= 0) {
451
- throw new Error('dimension must be a positive number');
452
- }
453
- } catch (err) {
454
- throw new MastraError(
455
- {
456
- id: 'STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED_INVALID_ARGS',
457
- domain: ErrorDomain.STORAGE,
458
- category: ErrorCategory.USER,
459
- details: { tableName: tableName || '', indexName, dimension, metric },
460
- },
461
- err,
462
- );
463
- }
464
-
465
- try {
466
- const tables = await this.lanceClient.tableNames();
467
- if (!tables.includes(tableName)) {
468
- throw new Error(
469
- `Table ${tableName} does not exist. Please create the table first by calling createTable() method.`,
470
- );
471
- }
472
-
473
- const table = await this.lanceClient.openTable(tableName);
474
-
475
- // Convert metric to LanceDB metric
476
- type LanceMetric = 'cosine' | 'l2' | 'dot';
477
- let metricType: LanceMetric | undefined;
478
- if (metric === 'euclidean') {
479
- metricType = 'l2';
480
- } else if (metric === 'dotproduct') {
481
- metricType = 'dot';
482
- } else if (metric === 'cosine') {
483
- metricType = 'cosine';
484
- }
485
-
486
- if (indexConfig.type === 'ivfflat') {
487
- await table.createIndex(indexName, {
488
- config: Index.ivfPq({
489
- numPartitions: indexConfig.numPartitions || 128,
490
- numSubVectors: indexConfig.numSubVectors || 16,
491
- distanceType: metricType,
492
- }),
493
- });
494
- } else {
495
- // Default to HNSW PQ index
496
- this.logger.debug('Creating HNSW PQ index with config:', indexConfig);
497
- await table.createIndex(indexName, {
498
- config: Index.hnswPq({
499
- m: indexConfig?.hnsw?.m || 16,
500
- efConstruction: indexConfig?.hnsw?.efConstruction || 100,
501
- distanceType: metricType,
502
- }),
503
- });
504
- }
505
- } catch (error: any) {
506
- throw new MastraError(
507
- {
508
- id: 'STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED',
509
- domain: ErrorDomain.STORAGE,
510
- category: ErrorCategory.THIRD_PARTY,
511
- details: { tableName: tableName || '', indexName, dimension },
512
- },
513
- error,
514
- );
515
- }
516
- }
517
-
518
- async listIndexes(): Promise<string[]> {
519
- if (!this.lanceClient) {
520
- throw new MastraError({
521
- id: 'STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED_INVALID_ARGS',
522
- domain: ErrorDomain.STORAGE,
523
- category: ErrorCategory.USER,
524
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
525
- details: { methodName: 'listIndexes' },
526
- });
527
- }
528
-
529
- try {
530
- const tables = await this.lanceClient.tableNames();
531
- const allIndices: string[] = [];
532
-
533
- for (const tableName of tables) {
534
- const table = await this.lanceClient.openTable(tableName);
535
- const tableIndices = await table.listIndices();
536
- allIndices.push(...tableIndices.map(index => index.name));
537
- }
538
-
539
- return allIndices;
540
- } catch (error: any) {
541
- throw new MastraError(
542
- {
543
- id: 'STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED',
544
- domain: ErrorDomain.STORAGE,
545
- category: ErrorCategory.THIRD_PARTY,
546
- },
547
- error,
548
- );
549
- }
550
- }
551
-
552
- async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
553
- try {
554
- if (!this.lanceClient) {
555
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
556
- }
557
-
558
- if (!indexName) {
559
- throw new Error('indexName is required');
560
- }
561
- } catch (err) {
562
- throw new MastraError(
563
- {
564
- id: 'STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED_INVALID_ARGS',
565
- domain: ErrorDomain.STORAGE,
566
- category: ErrorCategory.USER,
567
- details: { indexName },
568
- },
569
- err,
570
- );
571
- }
572
-
573
- try {
574
- const tables = await this.lanceClient.tableNames();
575
-
576
- for (const tableName of tables) {
577
- this.logger.debug('Checking table:' + tableName);
578
- const table = await this.lanceClient.openTable(tableName);
579
- const tableIndices = await table.listIndices();
580
- const foundIndex = tableIndices.find(index => index.name === indexName);
581
-
582
- if (foundIndex) {
583
- const stats = await table.indexStats(foundIndex.name);
584
-
585
- if (!stats) {
586
- throw new Error(`Index stats not found for index: ${indexName}`);
587
- }
588
-
589
- const schema = await table.schema();
590
- const vectorCol = foundIndex.columns[0] || 'vector';
591
-
592
- // Find the vector column in the schema
593
- const vectorField = schema.fields.find(field => field.name === vectorCol);
594
- const dimension = vectorField?.type?.['listSize'] || 0;
595
-
596
- return {
597
- dimension: dimension,
598
- metric: stats.distanceType as 'cosine' | 'euclidean' | 'dotproduct' | undefined,
599
- count: stats.numIndexedRows,
600
- };
601
- }
602
- }
603
-
604
- throw new Error(`IndexName: ${indexName} not found`);
605
- } catch (error: any) {
606
- throw new MastraError(
607
- {
608
- id: 'STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED',
609
- domain: ErrorDomain.STORAGE,
610
- category: ErrorCategory.THIRD_PARTY,
611
- details: { indexName },
612
- },
613
- error,
614
- );
615
- }
616
- }
617
-
618
- async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
619
- try {
620
- if (!this.lanceClient) {
621
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
622
- }
623
-
624
- if (!indexName) {
625
- throw new Error('indexName is required');
626
- }
627
- } catch (err) {
628
- throw new MastraError(
629
- {
630
- id: 'STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED_INVALID_ARGS',
631
- domain: ErrorDomain.STORAGE,
632
- category: ErrorCategory.USER,
633
- details: { indexName },
634
- },
635
- err,
636
- );
637
- }
638
- try {
639
- const tables = await this.lanceClient.tableNames();
640
-
641
- for (const tableName of tables) {
642
- const table = await this.lanceClient.openTable(tableName);
643
- const tableIndices = await table.listIndices();
644
- const foundIndex = tableIndices.find(index => index.name === indexName);
645
-
646
- if (foundIndex) {
647
- await table.dropIndex(indexName);
648
- return;
649
- }
650
- }
651
-
652
- throw new Error(`Index ${indexName} not found`);
653
- } catch (error: any) {
654
- throw new MastraError(
655
- {
656
- id: 'STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED',
657
- domain: ErrorDomain.STORAGE,
658
- category: ErrorCategory.THIRD_PARTY,
659
- details: { indexName },
660
- },
661
- error,
662
- );
663
- }
664
- }
665
-
666
- /**
667
- * Deletes all tables in the database
668
- */
669
- async deleteAllTables(): Promise<void> {
670
- if (!this.lanceClient) {
671
- throw new MastraError({
672
- id: 'STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED_INVALID_ARGS',
673
- domain: ErrorDomain.STORAGE,
674
- category: ErrorCategory.USER,
675
- details: { methodName: 'deleteAllTables' },
676
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
677
- });
678
- }
679
- try {
680
- await this.lanceClient.dropAllTables();
681
- } catch (error) {
682
- throw new MastraError(
683
- {
684
- id: 'STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED',
685
- domain: ErrorDomain.STORAGE,
686
- category: ErrorCategory.THIRD_PARTY,
687
- details: { methodName: 'deleteAllTables' },
688
- },
689
- error,
690
- );
691
- }
692
- }
693
-
694
- async deleteTable(tableName: string): Promise<void> {
695
- if (!this.lanceClient) {
696
- throw new MastraError({
697
- id: 'STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED_INVALID_ARGS',
698
- domain: ErrorDomain.STORAGE,
699
- category: ErrorCategory.USER,
700
- details: { tableName },
701
- text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
702
- });
703
- }
704
-
705
- try {
706
- await this.lanceClient.dropTable(tableName);
707
- } catch (error: any) {
708
- // throw new Error(`Failed to delete tables: ${error.message}`);
709
- throw new MastraError(
710
- {
711
- id: 'STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED',
712
- domain: ErrorDomain.STORAGE,
713
- category: ErrorCategory.THIRD_PARTY,
714
- details: { tableName },
715
- },
716
- error,
717
- );
718
- }
719
- }
720
-
721
- async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
722
- try {
723
- if (!this.lanceClient) {
724
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
725
- }
726
-
727
- if (!indexName) {
728
- throw new Error('indexName is required');
729
- }
730
-
731
- if (!id) {
732
- throw new Error('id is required');
733
- }
734
- } catch (err) {
735
- throw new MastraError(
736
- {
737
- id: 'STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED_INVALID_ARGS',
738
- domain: ErrorDomain.STORAGE,
739
- category: ErrorCategory.USER,
740
- details: { indexName, id },
741
- },
742
- err,
743
- );
744
- }
745
-
746
- try {
747
- // In LanceDB, the indexName is actually a column name in a table
748
- // We need to find which table has this column as an index
749
- const tables = await this.lanceClient.tableNames();
750
-
751
- for (const tableName of tables) {
752
- this.logger.debug('Checking table:' + tableName);
753
- const table = await this.lanceClient.openTable(tableName);
754
-
755
- try {
756
- const schema = await table.schema();
757
- const hasColumn = schema.fields.some(field => field.name === indexName);
758
-
759
- if (hasColumn) {
760
- this.logger.debug(`Found column ${indexName} in table ${tableName}`);
761
-
762
- // First, query the existing record to preserve values that aren't being updated
763
- const existingRecord = await table
764
- .query()
765
- .where(`id = '${id}'`)
766
- .select(schema.fields.map(field => field.name))
767
- .limit(1)
768
- .toArray();
769
-
770
- if (existingRecord.length === 0) {
771
- throw new Error(`Record with id '${id}' not found in table ${tableName}`);
772
- }
773
-
774
- // Create a clean data object for update
775
- const rowData: Record<string, any> = {
776
- id,
777
- };
778
-
779
- // Copy all existing field values except special fields
780
- Object.entries(existingRecord[0]).forEach(([key, value]) => {
781
- // Skip special fields
782
- if (key !== 'id' && key !== '_distance') {
783
- // Handle vector field specially to avoid nested properties
784
- if (key === indexName) {
785
- // If we're about to update this vector anyway, skip copying
786
- if (!update.vector) {
787
- // Ensure vector is a plain array
788
- if (Array.isArray(value)) {
789
- rowData[key] = [...value];
790
- } else if (typeof value === 'object' && value !== null) {
791
- // Handle vector objects by converting to array if needed
792
- rowData[key] = Array.from(value as any[]);
793
- } else {
794
- rowData[key] = value;
795
- }
796
- }
797
- } else {
798
- rowData[key] = value;
799
- }
800
- }
801
- });
802
-
803
- // Apply the vector update if provided
804
- if (update.vector) {
805
- rowData[indexName] = update.vector;
806
- }
807
-
808
- // Apply metadata updates if provided
809
- if (update.metadata) {
810
- Object.entries(update.metadata).forEach(([key, value]) => {
811
- rowData[`metadata_${key}`] = value;
812
- });
813
- }
814
-
815
- // Update the record
816
- await table.add([rowData], { mode: 'overwrite' });
817
- return;
818
- }
819
- } catch (err) {
820
- this.logger.error(`Error checking schema for table ${tableName}:` + err);
821
- // Continue to the next table if there's an error
822
- continue;
823
- }
824
- }
825
-
826
- throw new Error(`No table found with column/index '${indexName}'`);
827
- } catch (error: any) {
828
- throw new MastraError(
829
- {
830
- id: 'STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED',
831
- domain: ErrorDomain.STORAGE,
832
- category: ErrorCategory.THIRD_PARTY,
833
- details: { indexName, id, hasVector: !!update.vector, hasMetadata: !!update.metadata },
834
- },
835
- error,
836
- );
837
- }
838
- }
839
-
840
- async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
841
- try {
842
- if (!this.lanceClient) {
843
- throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
844
- }
845
-
846
- if (!indexName) {
847
- throw new Error('indexName is required');
848
- }
849
-
850
- if (!id) {
851
- throw new Error('id is required');
852
- }
853
- } catch (err) {
854
- throw new MastraError(
855
- {
856
- id: 'STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED_INVALID_ARGS',
857
- domain: ErrorDomain.STORAGE,
858
- category: ErrorCategory.USER,
859
- details: { indexName, id },
860
- },
861
- err,
862
- );
863
- }
864
-
865
- try {
866
- // In LanceDB, the indexName is actually a column name in a table
867
- // We need to find which table has this column as an index
868
- const tables = await this.lanceClient.tableNames();
869
-
870
- for (const tableName of tables) {
871
- this.logger.debug('Checking table:' + tableName);
872
- const table = await this.lanceClient.openTable(tableName);
873
-
874
- try {
875
- // Try to get the schema to check if this table has the column we're looking for
876
- const schema = await table.schema();
877
- const hasColumn = schema.fields.some(field => field.name === indexName);
878
-
879
- if (hasColumn) {
880
- this.logger.debug(`Found column ${indexName} in table ${tableName}`);
881
- await table.delete(`id = '${id}'`);
882
- return;
883
- }
884
- } catch (err) {
885
- this.logger.error(`Error checking schema for table ${tableName}:` + err);
886
- // Continue to the next table if there's an error
887
- continue;
888
- }
889
- }
890
-
891
- throw new Error(`No table found with column/index '${indexName}'`);
892
- } catch (error: any) {
893
- throw new MastraError(
894
- {
895
- id: 'STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED',
896
- domain: ErrorDomain.STORAGE,
897
- category: ErrorCategory.THIRD_PARTY,
898
- details: { indexName, id },
899
- },
900
- error,
901
- );
902
- }
903
- }
904
-
905
- /**
906
- * Converts a flattened object with keys using underscore notation back to a nested object.
907
- * Example: { name: 'test', details_text: 'test' } → { name: 'test', details: { text: 'test' } }
908
- */
909
- private unflattenObject(obj: Record<string, any>): Record<string, any> {
910
- const result: Record<string, any> = {};
911
-
912
- Object.keys(obj).forEach(key => {
913
- const value = obj[key];
914
- const parts = key.split('_');
915
-
916
- // Start with the result object
917
- let current = result;
918
-
919
- // Process all parts except the last one
920
- for (let i = 0; i < parts.length - 1; i++) {
921
- const part = parts[i];
922
- // Skip empty parts
923
- if (!part) continue;
924
-
925
- // Create nested object if it doesn't exist
926
- if (!current[part] || typeof current[part] !== 'object') {
927
- current[part] = {};
928
- }
929
- current = current[part];
930
- }
931
-
932
- // Set the value at the last part
933
- const lastPart = parts[parts.length - 1];
934
- if (lastPart) {
935
- current[lastPart] = value;
936
- }
937
- });
938
-
939
- return result;
940
- }
941
- }