@mastra/lance 0.2.0 → 0.2.1

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,35 +1,34 @@
1
1
  import { connect } from '@lancedb/lancedb';
2
- import type { Connection, ConnectionOptions, SchemaLike, FieldLike } from '@lancedb/lancedb';
2
+ import type { Connection, ConnectionOptions } from '@lancedb/lancedb';
3
3
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
4
- import { MessageList } from '@mastra/core/agent';
5
4
  import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
6
5
  import type { MastraMessageV1, MastraMessageV2, StorageThreadType, TraceType } from '@mastra/core/memory';
7
- import {
8
- MastraStorage,
9
- TABLE_EVALS,
10
- TABLE_MESSAGES,
11
- TABLE_THREADS,
12
- TABLE_TRACES,
13
- TABLE_WORKFLOW_SNAPSHOT,
14
- } from '@mastra/core/storage';
6
+ import type { ScoreRowData } from '@mastra/core/scores';
7
+ import { MastraStorage } from '@mastra/core/storage';
15
8
  import type {
16
9
  TABLE_NAMES,
17
10
  PaginationInfo,
18
11
  StorageGetMessagesArg,
19
- StorageGetTracesArg,
20
12
  StorageColumn,
21
13
  EvalRow,
22
- WorkflowRun,
23
14
  WorkflowRuns,
15
+ StoragePagination,
16
+ StorageDomains,
17
+ StorageGetTracesPaginatedArg,
18
+ StorageResourceType,
24
19
  } from '@mastra/core/storage';
25
20
  import type { Trace } from '@mastra/core/telemetry';
26
21
  import type { WorkflowRunState } from '@mastra/core/workflows';
27
- import type { DataType } from 'apache-arrow';
28
- import { Utf8, Int32, Float32, Binary, Schema, Field, Float64 } from 'apache-arrow';
22
+ import { StoreLegacyEvalsLance } from './domains/legacy-evals';
23
+ import { StoreMemoryLance } from './domains/memory';
24
+ import { StoreOperationsLance } from './domains/operations';
25
+ import { StoreScoresLance } from './domains/scores';
26
+ import { StoreTracesLance } from './domains/traces';
27
+ import { StoreWorkflowsLance } from './domains/workflows';
29
28
 
30
29
  export class LanceStorage extends MastraStorage {
30
+ stores: StorageDomains;
31
31
  private lanceClient!: Connection;
32
-
33
32
  /**
34
33
  * Creates a new instance of LanceStorage
35
34
  * @param uri The URI to connect to LanceDB
@@ -56,6 +55,15 @@ export class LanceStorage extends MastraStorage {
56
55
  const instance = new LanceStorage(name);
57
56
  try {
58
57
  instance.lanceClient = await connect(uri, options);
58
+ const operations = new StoreOperationsLance({ client: instance.lanceClient });
59
+ instance.stores = {
60
+ operations: new StoreOperationsLance({ client: instance.lanceClient }),
61
+ workflows: new StoreWorkflowsLance({ client: instance.lanceClient }),
62
+ traces: new StoreTracesLance({ client: instance.lanceClient, operations }),
63
+ scores: new StoreScoresLance({ client: instance.lanceClient }),
64
+ memory: new StoreMemoryLance({ client: instance.lanceClient, operations }),
65
+ legacyEvals: new StoreLegacyEvalsLance({ client: instance.lanceClient }),
66
+ };
59
67
  return instance;
60
68
  } catch (e: any) {
61
69
  throw new MastraError(
@@ -71,23 +79,22 @@ export class LanceStorage extends MastraStorage {
71
79
  }
72
80
  }
73
81
 
74
- private getPrimaryKeys(tableName: TABLE_NAMES): string[] {
75
- let primaryId: string[] = ['id'];
76
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
77
- primaryId = ['workflow_name', 'run_id'];
78
- } else if (tableName === TABLE_EVALS) {
79
- primaryId = ['agent_name', 'metric_name', 'run_id'];
80
- }
81
-
82
- return primaryId;
83
- }
84
-
85
82
  /**
86
83
  * @internal
87
84
  * Private constructor to enforce using the create factory method
88
85
  */
89
86
  private constructor(name: string) {
90
87
  super({ name });
88
+ const operations = new StoreOperationsLance({ client: this.lanceClient });
89
+
90
+ this.stores = {
91
+ operations: new StoreOperationsLance({ client: this.lanceClient }),
92
+ workflows: new StoreWorkflowsLance({ client: this.lanceClient }),
93
+ traces: new StoreTracesLance({ client: this.lanceClient, operations }),
94
+ scores: new StoreScoresLance({ client: this.lanceClient }),
95
+ legacyEvals: new StoreLegacyEvalsLance({ client: this.lanceClient }),
96
+ memory: new StoreMemoryLance({ client: this.lanceClient, operations }),
97
+ };
91
98
  }
92
99
 
93
100
  async createTable({
@@ -97,674 +104,47 @@ export class LanceStorage extends MastraStorage {
97
104
  tableName: TABLE_NAMES;
98
105
  schema: Record<string, StorageColumn>;
99
106
  }): Promise<void> {
100
- try {
101
- if (!this.lanceClient) {
102
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
103
- }
104
- if (!tableName) {
105
- throw new Error('tableName is required for createTable.');
106
- }
107
- if (!schema) {
108
- throw new Error('schema is required for createTable.');
109
- }
110
- } catch (error) {
111
- throw new MastraError(
112
- {
113
- id: 'STORAGE_LANCE_STORAGE_CREATE_TABLE_INVALID_ARGS',
114
- domain: ErrorDomain.STORAGE,
115
- category: ErrorCategory.USER,
116
- details: { tableName },
117
- },
118
- error,
119
- );
120
- }
121
-
122
- try {
123
- const arrowSchema = this.translateSchema(schema);
124
- await this.lanceClient.createEmptyTable(tableName, arrowSchema);
125
- } catch (error: any) {
126
- throw new MastraError(
127
- {
128
- id: 'STORAGE_LANCE_STORAGE_CREATE_TABLE_FAILED',
129
- domain: ErrorDomain.STORAGE,
130
- category: ErrorCategory.THIRD_PARTY,
131
- details: { tableName },
132
- },
133
- error,
134
- );
135
- }
136
- }
137
-
138
- private translateSchema(schema: Record<string, StorageColumn>): Schema {
139
- const fields = Object.entries(schema).map(([name, column]) => {
140
- // Convert string type to Arrow DataType
141
- let arrowType: DataType;
142
- switch (column.type.toLowerCase()) {
143
- case 'text':
144
- case 'uuid':
145
- arrowType = new Utf8();
146
- break;
147
- case 'int':
148
- case 'integer':
149
- arrowType = new Int32();
150
- break;
151
- case 'bigint':
152
- arrowType = new Float64();
153
- break;
154
- case 'float':
155
- arrowType = new Float32();
156
- break;
157
- case 'jsonb':
158
- case 'json':
159
- arrowType = new Utf8();
160
- break;
161
- case 'binary':
162
- arrowType = new Binary();
163
- break;
164
- case 'timestamp':
165
- arrowType = new Float64();
166
- break;
167
- default:
168
- // Default to string for unknown types
169
- arrowType = new Utf8();
170
- }
171
-
172
- // Create a field with the appropriate arrow type
173
- return new Field(name, arrowType, column.nullable ?? true);
174
- });
175
-
176
- return new Schema(fields);
177
- }
178
-
179
- /**
180
- * Drop a table if it exists
181
- * @param tableName Name of the table to drop
182
- */
183
- async dropTable(tableName: TABLE_NAMES): Promise<void> {
184
- try {
185
- if (!this.lanceClient) {
186
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
187
- }
188
- if (!tableName) {
189
- throw new Error('tableName is required for dropTable.');
190
- }
191
- } catch (validationError: any) {
192
- throw new MastraError(
193
- {
194
- id: 'STORAGE_LANCE_STORAGE_DROP_TABLE_INVALID_ARGS',
195
- domain: ErrorDomain.STORAGE,
196
- category: ErrorCategory.USER,
197
- text: validationError.message,
198
- details: { tableName },
199
- },
200
- validationError,
201
- );
202
- }
203
-
204
- try {
205
- await this.lanceClient.dropTable(tableName);
206
- } catch (error: any) {
207
- if (error.toString().includes('was not found') || error.message?.includes('Table not found')) {
208
- this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
209
- return;
210
- }
211
- throw new MastraError(
212
- {
213
- id: 'STORAGE_LANCE_STORAGE_DROP_TABLE_FAILED',
214
- domain: ErrorDomain.STORAGE,
215
- category: ErrorCategory.THIRD_PARTY,
216
- details: { tableName },
217
- },
218
- error,
219
- );
220
- }
221
- }
222
-
223
- /**
224
- * Get table schema
225
- * @param tableName Name of the table
226
- * @returns Table schema
227
- */
228
- async getTableSchema(tableName: TABLE_NAMES): Promise<SchemaLike> {
229
- try {
230
- if (!this.lanceClient) {
231
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
232
- }
233
- if (!tableName) {
234
- throw new Error('tableName is required for getTableSchema.');
235
- }
236
- } catch (validationError: any) {
237
- throw new MastraError(
238
- {
239
- id: 'STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_INVALID_ARGS',
240
- domain: ErrorDomain.STORAGE,
241
- category: ErrorCategory.USER,
242
- text: validationError.message,
243
- details: { tableName },
244
- },
245
- validationError,
246
- );
247
- }
248
-
249
- try {
250
- const table = await this.lanceClient.openTable(tableName);
251
- const rawSchema = await table.schema();
252
- const fields = rawSchema.fields as FieldLike[];
253
-
254
- // Convert schema to SchemaLike format
255
- return {
256
- fields,
257
- metadata: new Map<string, string>(),
258
- get names() {
259
- return fields.map((field: FieldLike) => field.name);
260
- },
261
- };
262
- } catch (error: any) {
263
- throw new MastraError(
264
- {
265
- id: 'STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_FAILED',
266
- domain: ErrorDomain.STORAGE,
267
- category: ErrorCategory.THIRD_PARTY,
268
- details: { tableName },
269
- },
270
- error,
271
- );
272
- }
107
+ return this.stores.operations.createTable({ tableName, schema });
273
108
  }
274
109
 
275
- protected getDefaultValue(type: StorageColumn['type']): string {
276
- switch (type) {
277
- case 'text':
278
- return "''";
279
- case 'timestamp':
280
- return 'CURRENT_TIMESTAMP';
281
- case 'integer':
282
- case 'bigint':
283
- return '0';
284
- case 'jsonb':
285
- return "'{}'";
286
- case 'uuid':
287
- return "''";
288
- default:
289
- return super.getDefaultValue(type);
290
- }
110
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
111
+ return this.stores.operations.dropTable({ tableName });
291
112
  }
292
113
 
293
- /**
294
- * Alters table schema to add columns if they don't exist
295
- * @param tableName Name of the table
296
- * @param schema Schema of the table
297
- * @param ifNotExists Array of column names to add if they don't exist
298
- */
299
114
  async alterTable({
300
115
  tableName,
301
116
  schema,
302
117
  ifNotExists,
303
118
  }: {
304
- tableName: string;
119
+ tableName: TABLE_NAMES;
305
120
  schema: Record<string, StorageColumn>;
306
121
  ifNotExists: string[];
307
122
  }): Promise<void> {
308
- try {
309
- if (!this.lanceClient) {
310
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
311
- }
312
- if (!tableName) {
313
- throw new Error('tableName is required for alterTable.');
314
- }
315
- if (!schema) {
316
- throw new Error('schema is required for alterTable.');
317
- }
318
- if (!ifNotExists || ifNotExists.length === 0) {
319
- this.logger.debug('No columns specified to add in alterTable, skipping.');
320
- return;
321
- }
322
- } catch (validationError: any) {
323
- throw new MastraError(
324
- {
325
- id: 'STORAGE_LANCE_STORAGE_ALTER_TABLE_INVALID_ARGS',
326
- domain: ErrorDomain.STORAGE,
327
- category: ErrorCategory.USER,
328
- text: validationError.message,
329
- details: { tableName },
330
- },
331
- validationError,
332
- );
333
- }
334
-
335
- try {
336
- const table = await this.lanceClient.openTable(tableName);
337
- const currentSchema = await table.schema();
338
- const existingFields = new Set(currentSchema.fields.map((f: any) => f.name));
339
-
340
- const typeMap: Record<string, string> = {
341
- text: 'string',
342
- integer: 'int',
343
- bigint: 'bigint',
344
- timestamp: 'timestamp',
345
- jsonb: 'string',
346
- uuid: 'string',
347
- };
348
-
349
- // Find columns to add
350
- const columnsToAdd = ifNotExists
351
- .filter(col => schema[col] && !existingFields.has(col))
352
- .map(col => {
353
- const colDef = schema[col];
354
- return {
355
- name: col,
356
- valueSql: colDef?.nullable
357
- ? `cast(NULL as ${typeMap[colDef.type ?? 'text']})`
358
- : `cast(${this.getDefaultValue(colDef?.type ?? 'text')} as ${typeMap[colDef?.type ?? 'text']})`,
359
- };
360
- });
361
-
362
- if (columnsToAdd.length > 0) {
363
- await table.addColumns(columnsToAdd);
364
- this.logger?.info?.(`Added columns [${columnsToAdd.map(c => c.name).join(', ')}] to table ${tableName}`);
365
- }
366
- } catch (error: any) {
367
- throw new MastraError(
368
- {
369
- id: 'STORAGE_LANCE_STORAGE_ALTER_TABLE_FAILED',
370
- domain: ErrorDomain.STORAGE,
371
- category: ErrorCategory.THIRD_PARTY,
372
- details: { tableName },
373
- },
374
- error,
375
- );
376
- }
123
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
377
124
  }
378
125
 
379
126
  async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
380
- try {
381
- if (!this.lanceClient) {
382
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
383
- }
384
- if (!tableName) {
385
- throw new Error('tableName is required for clearTable.');
386
- }
387
- } catch (validationError: any) {
388
- throw new MastraError(
389
- {
390
- id: 'STORAGE_LANCE_STORAGE_CLEAR_TABLE_INVALID_ARGS',
391
- domain: ErrorDomain.STORAGE,
392
- category: ErrorCategory.USER,
393
- text: validationError.message,
394
- details: { tableName },
395
- },
396
- validationError,
397
- );
398
- }
399
-
400
- try {
401
- const table = await this.lanceClient.openTable(tableName);
402
-
403
- // delete function always takes a predicate as an argument, so we use '1=1' to delete all records because it is always true.
404
- await table.delete('1=1');
405
- } catch (error: any) {
406
- throw new MastraError(
407
- {
408
- id: 'STORAGE_LANCE_STORAGE_CLEAR_TABLE_FAILED',
409
- domain: ErrorDomain.STORAGE,
410
- category: ErrorCategory.THIRD_PARTY,
411
- details: { tableName },
412
- },
413
- error,
414
- );
415
- }
127
+ return this.stores.operations.clearTable({ tableName });
416
128
  }
417
129
 
418
- /**
419
- * Insert a single record into a table. This function overwrites the existing record if it exists. Use this function for inserting records into tables with custom schemas.
420
- * @param tableName The name of the table to insert into.
421
- * @param record The record to insert.
422
- */
423
- async insert({ tableName, record }: { tableName: string; record: Record<string, any> }): Promise<void> {
424
- try {
425
- if (!this.lanceClient) {
426
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
427
- }
428
- if (!tableName) {
429
- throw new Error('tableName is required for insert.');
430
- }
431
- if (!record || Object.keys(record).length === 0) {
432
- throw new Error('record is required and cannot be empty for insert.');
433
- }
434
- } catch (validationError: any) {
435
- throw new MastraError(
436
- {
437
- id: 'STORAGE_LANCE_STORAGE_INSERT_INVALID_ARGS',
438
- domain: ErrorDomain.STORAGE,
439
- category: ErrorCategory.USER,
440
- text: validationError.message,
441
- details: { tableName },
442
- },
443
- validationError,
444
- );
445
- }
446
-
447
- try {
448
- const table = await this.lanceClient.openTable(tableName);
449
-
450
- const primaryId = this.getPrimaryKeys(tableName as TABLE_NAMES);
451
-
452
- const processedRecord = { ...record };
453
-
454
- for (const key in processedRecord) {
455
- if (
456
- processedRecord[key] !== null &&
457
- typeof processedRecord[key] === 'object' &&
458
- !(processedRecord[key] instanceof Date)
459
- ) {
460
- this.logger.debug('Converting object to JSON string: ', processedRecord[key]);
461
- processedRecord[key] = JSON.stringify(processedRecord[key]);
462
- }
463
- }
464
-
465
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
466
- } catch (error: any) {
467
- throw new MastraError(
468
- {
469
- id: 'STORAGE_LANCE_STORAGE_INSERT_FAILED',
470
- domain: ErrorDomain.STORAGE,
471
- category: ErrorCategory.THIRD_PARTY,
472
- details: { tableName },
473
- },
474
- error,
475
- );
476
- }
130
+ async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
131
+ return this.stores.operations.insert({ tableName, record });
477
132
  }
478
133
 
479
- /**
480
- * Insert multiple records into a table. This function overwrites the existing records if they exist. Use this function for inserting records into tables with custom schemas.
481
- * @param tableName The name of the table to insert into.
482
- * @param records The records to insert.
483
- */
484
- async batchInsert({ tableName, records }: { tableName: string; records: Record<string, any>[] }): Promise<void> {
485
- try {
486
- if (!this.lanceClient) {
487
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
488
- }
489
- if (!tableName) {
490
- throw new Error('tableName is required for batchInsert.');
491
- }
492
- if (!records || records.length === 0) {
493
- throw new Error('records array is required and cannot be empty for batchInsert.');
494
- }
495
- } catch (validationError: any) {
496
- throw new MastraError(
497
- {
498
- id: 'STORAGE_LANCE_STORAGE_BATCH_INSERT_INVALID_ARGS',
499
- domain: ErrorDomain.STORAGE,
500
- category: ErrorCategory.USER,
501
- text: validationError.message,
502
- details: { tableName },
503
- },
504
- validationError,
505
- );
506
- }
507
-
508
- try {
509
- const table = await this.lanceClient.openTable(tableName);
510
-
511
- const primaryId = this.getPrimaryKeys(tableName as TABLE_NAMES);
512
-
513
- const processedRecords = records.map(record => {
514
- const processedRecord = { ...record };
515
-
516
- // Convert values based on schema type
517
- for (const key in processedRecord) {
518
- // Skip null/undefined values
519
- if (processedRecord[key] == null) continue;
520
-
521
- if (
522
- processedRecord[key] !== null &&
523
- typeof processedRecord[key] === 'object' &&
524
- !(processedRecord[key] instanceof Date)
525
- ) {
526
- processedRecord[key] = JSON.stringify(processedRecord[key]);
527
- }
528
- }
529
-
530
- return processedRecord;
531
- });
532
-
533
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
534
- } catch (error: any) {
535
- throw new MastraError(
536
- {
537
- id: 'STORAGE_LANCE_STORAGE_BATCH_INSERT_FAILED',
538
- domain: ErrorDomain.STORAGE,
539
- category: ErrorCategory.THIRD_PARTY,
540
- details: { tableName },
541
- },
542
- error,
543
- );
544
- }
134
+ async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
135
+ return this.stores.operations.batchInsert({ tableName, records });
545
136
  }
546
137
 
547
- /**
548
- * Load a record from the database by its key(s)
549
- * @param tableName The name of the table to query
550
- * @param keys Record of key-value pairs to use for lookup
551
- * @throws Error if invalid types are provided for keys
552
- * @returns The loaded record with proper type conversions, or null if not found
553
- */
554
138
  async load({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, any> }): Promise<any> {
555
- try {
556
- if (!this.lanceClient) {
557
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
558
- }
559
- if (!tableName) {
560
- throw new Error('tableName is required for load.');
561
- }
562
- if (!keys || Object.keys(keys).length === 0) {
563
- throw new Error('keys are required and cannot be empty for load.');
564
- }
565
- } catch (validationError: any) {
566
- throw new MastraError(
567
- {
568
- id: 'STORAGE_LANCE_STORAGE_LOAD_INVALID_ARGS',
569
- domain: ErrorDomain.STORAGE,
570
- category: ErrorCategory.USER,
571
- text: validationError.message,
572
- details: { tableName },
573
- },
574
- validationError,
575
- );
576
- }
577
-
578
- try {
579
- const table = await this.lanceClient.openTable(tableName);
580
- const tableSchema = await this.getTableSchema(tableName);
581
- const query = table.query();
582
-
583
- // Build filter condition with 'and' between all conditions
584
- if (Object.keys(keys).length > 0) {
585
- // Validate key types against schema
586
- this.validateKeyTypes(keys, tableSchema);
587
-
588
- const filterConditions = Object.entries(keys)
589
- .map(([key, value]) => {
590
- // Check if key is in camelCase and wrap it in backticks if it is
591
- const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
592
- const quotedKey = isCamelCase ? `\`${key}\`` : key;
593
-
594
- // Handle different types appropriately
595
- if (typeof value === 'string') {
596
- return `${quotedKey} = '${value}'`;
597
- } else if (value === null) {
598
- return `${quotedKey} IS NULL`;
599
- } else {
600
- // For numbers, booleans, etc.
601
- return `${quotedKey} = ${value}`;
602
- }
603
- })
604
- .join(' AND ');
605
-
606
- this.logger.debug('where clause generated: ' + filterConditions);
607
- query.where(filterConditions);
608
- }
609
-
610
- const result = await query.limit(1).toArray();
611
-
612
- if (result.length === 0) {
613
- this.logger.debug('No record found');
614
- return null;
615
- }
616
-
617
- // Process the result with type conversions
618
- return this.processResultWithTypeConversion(result[0], tableSchema);
619
- } catch (error: any) {
620
- // If it's already a MastraError (e.g. from validateKeyTypes if we change it later), rethrow
621
- if (error instanceof MastraError) throw error;
622
- throw new MastraError(
623
- {
624
- id: 'STORAGE_LANCE_STORAGE_LOAD_FAILED',
625
- domain: ErrorDomain.STORAGE,
626
- category: ErrorCategory.THIRD_PARTY,
627
- details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? '' },
628
- },
629
- error,
630
- );
631
- }
632
- }
633
-
634
- /**
635
- * Validates that key types match the schema definition
636
- * @param keys The keys to validate
637
- * @param tableSchema The table schema to validate against
638
- * @throws Error if a key has an incompatible type
639
- */
640
- private validateKeyTypes(keys: Record<string, any>, tableSchema: SchemaLike): void {
641
- // Create a map of field names to their expected types
642
- const fieldTypes = new Map(
643
- tableSchema.fields.map((field: any) => [field.name, field.type?.toString().toLowerCase()]),
644
- );
645
-
646
- for (const [key, value] of Object.entries(keys)) {
647
- const fieldType = fieldTypes.get(key);
648
-
649
- if (!fieldType) {
650
- throw new Error(`Field '${key}' does not exist in table schema`);
651
- }
652
-
653
- // Type validation
654
- if (value !== null) {
655
- if ((fieldType.includes('int') || fieldType.includes('bigint')) && typeof value !== 'number') {
656
- throw new Error(`Expected numeric value for field '${key}', got ${typeof value}`);
657
- }
658
-
659
- if (fieldType.includes('utf8') && typeof value !== 'string') {
660
- throw new Error(`Expected string value for field '${key}', got ${typeof value}`);
661
- }
662
-
663
- if (fieldType.includes('timestamp') && !(value instanceof Date) && typeof value !== 'string') {
664
- throw new Error(`Expected Date or string value for field '${key}', got ${typeof value}`);
665
- }
666
- }
667
- }
668
- }
669
-
670
- /**
671
- * Process a database result with appropriate type conversions based on the table schema
672
- * @param rawResult The raw result object from the database
673
- * @param tableSchema The schema of the table containing type information
674
- * @returns Processed result with correct data types
675
- */
676
- private processResultWithTypeConversion(
677
- rawResult: Record<string, any> | Record<string, any>[],
678
- tableSchema: SchemaLike,
679
- ): Record<string, any> | Record<string, any>[] {
680
- // Build a map of field names to their schema types
681
- const fieldTypeMap = new Map();
682
- tableSchema.fields.forEach((field: any) => {
683
- const fieldName = field.name;
684
- const fieldTypeStr = field.type.toString().toLowerCase();
685
- fieldTypeMap.set(fieldName, fieldTypeStr);
686
- });
687
-
688
- // Handle array case
689
- if (Array.isArray(rawResult)) {
690
- return rawResult.map(item => this.processResultWithTypeConversion(item, tableSchema));
691
- }
692
-
693
- // Handle single record case
694
- const processedResult = { ...rawResult };
695
-
696
- // Convert each field according to its schema type
697
- for (const key in processedResult) {
698
- const fieldTypeStr = fieldTypeMap.get(key);
699
- if (!fieldTypeStr) continue;
700
-
701
- // Skip conversion for ID fields - preserve their original format
702
- // if (key === 'id') {
703
- // continue;
704
- // }
705
-
706
- // Only try to convert string values
707
- if (typeof processedResult[key] === 'string') {
708
- // Numeric types
709
- if (fieldTypeStr.includes('int32') || fieldTypeStr.includes('float32')) {
710
- if (!isNaN(Number(processedResult[key]))) {
711
- processedResult[key] = Number(processedResult[key]);
712
- }
713
- } else if (fieldTypeStr.includes('int64')) {
714
- processedResult[key] = Number(processedResult[key]);
715
- } else if (fieldTypeStr.includes('utf8')) {
716
- try {
717
- processedResult[key] = JSON.parse(processedResult[key]);
718
- } catch (e) {
719
- // If JSON parsing fails, keep the original string
720
- this.logger.debug(`Failed to parse JSON for key ${key}: ${e}`);
721
- }
722
- }
723
- } else if (typeof processedResult[key] === 'bigint') {
724
- // Convert BigInt values to regular numbers for application layer
725
- processedResult[key] = Number(processedResult[key]);
726
- }
727
- }
728
-
729
- return processedResult;
139
+ return this.stores.operations.load({ tableName, keys });
730
140
  }
731
141
 
732
- getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
733
- try {
734
- return this.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
735
- } catch (error: any) {
736
- throw new MastraError(
737
- {
738
- id: 'LANCE_STORE_GET_THREAD_BY_ID_FAILED',
739
- domain: ErrorDomain.STORAGE,
740
- category: ErrorCategory.THIRD_PARTY,
741
- },
742
- error,
743
- );
744
- }
142
+ async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
143
+ return this.stores.memory.getThreadById({ threadId });
745
144
  }
746
145
 
747
146
  async getThreadsByResourceId({ resourceId }: { resourceId: string }): Promise<StorageThreadType[]> {
748
- try {
749
- const table = await this.lanceClient.openTable(TABLE_THREADS);
750
- // fetches all threads with the given resourceId
751
- const query = table.query().where(`\`resourceId\` = '${resourceId}'`);
752
-
753
- const records = await query.toArray();
754
- return this.processResultWithTypeConversion(
755
- records,
756
- await this.getTableSchema(TABLE_THREADS),
757
- ) as StorageThreadType[];
758
- } catch (error: any) {
759
- throw new MastraError(
760
- {
761
- id: 'LANCE_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED',
762
- domain: ErrorDomain.STORAGE,
763
- category: ErrorCategory.THIRD_PARTY,
764
- },
765
- error,
766
- );
767
- }
147
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
768
148
  }
769
149
 
770
150
  /**
@@ -773,22 +153,7 @@ export class LanceStorage extends MastraStorage {
773
153
  * @returns The saved thread
774
154
  */
775
155
  async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
776
- try {
777
- const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
778
- const table = await this.lanceClient.openTable(TABLE_THREADS);
779
- await table.add([record], { mode: 'append' });
780
-
781
- return thread;
782
- } catch (error: any) {
783
- throw new MastraError(
784
- {
785
- id: 'LANCE_STORE_SAVE_THREAD_FAILED',
786
- domain: ErrorDomain.STORAGE,
787
- category: ErrorCategory.THIRD_PARTY,
788
- },
789
- error,
790
- );
791
- }
156
+ return this.stores.memory.saveThread({ thread });
792
157
  }
793
158
 
794
159
  async updateThread({
@@ -800,44 +165,40 @@ export class LanceStorage extends MastraStorage {
800
165
  title: string;
801
166
  metadata: Record<string, unknown>;
802
167
  }): Promise<StorageThreadType> {
803
- try {
804
- const record = { id, title, metadata: JSON.stringify(metadata) };
805
- const table = await this.lanceClient.openTable(TABLE_THREADS);
806
- await table.mergeInsert('id').whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
168
+ return this.stores.memory.updateThread({ id, title, metadata });
169
+ }
807
170
 
808
- const query = table.query().where(`id = '${id}'`);
171
+ async deleteThread({ threadId }: { threadId: string }): Promise<void> {
172
+ return this.stores.memory.deleteThread({ threadId });
173
+ }
809
174
 
810
- const records = await query.toArray();
811
- return this.processResultWithTypeConversion(
812
- records[0],
813
- await this.getTableSchema(TABLE_THREADS),
814
- ) as StorageThreadType;
815
- } catch (error: any) {
816
- throw new MastraError(
817
- {
818
- id: 'LANCE_STORE_UPDATE_THREAD_FAILED',
819
- domain: ErrorDomain.STORAGE,
820
- category: ErrorCategory.THIRD_PARTY,
821
- },
822
- error,
823
- );
824
- }
175
+ public get supports() {
176
+ return {
177
+ selectByIncludeResourceScope: true,
178
+ resourceWorkingMemory: true,
179
+ hasColumn: true,
180
+ createTable: true,
181
+ };
825
182
  }
826
183
 
827
- async deleteThread({ threadId }: { threadId: string }): Promise<void> {
828
- try {
829
- const table = await this.lanceClient.openTable(TABLE_THREADS);
830
- await table.delete(`id = '${threadId}'`);
831
- } catch (error: any) {
832
- throw new MastraError(
833
- {
834
- id: 'LANCE_STORE_DELETE_THREAD_FAILED',
835
- domain: ErrorDomain.STORAGE,
836
- category: ErrorCategory.THIRD_PARTY,
837
- },
838
- error,
839
- );
840
- }
184
+ async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
185
+ return this.stores.memory.getResourceById({ resourceId });
186
+ }
187
+
188
+ async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
189
+ return this.stores.memory.saveResource({ resource });
190
+ }
191
+
192
+ async updateResource({
193
+ resourceId,
194
+ workingMemory,
195
+ metadata,
196
+ }: {
197
+ resourceId: string;
198
+ workingMemory?: string;
199
+ metadata?: Record<string, unknown>;
200
+ }): Promise<StorageResourceType> {
201
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
841
202
  }
842
203
 
843
204
  /**
@@ -867,6 +228,7 @@ export class LanceStorage extends MastraStorage {
867
228
 
868
229
  for (const item of messagesWithContext) {
869
230
  const messageIndex = messageIndexMap.get(item.id);
231
+
870
232
  if (messageIndex !== undefined) {
871
233
  // Add previous messages if requested
872
234
  if (item.withPreviousMessages) {
@@ -925,76 +287,7 @@ export class LanceStorage extends MastraStorage {
925
287
  format,
926
288
  threadConfig,
927
289
  }: StorageGetMessagesArg & { format?: 'v1' | 'v2' }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
928
- try {
929
- if (threadConfig) {
930
- throw new Error('ThreadConfig is not supported by LanceDB storage');
931
- }
932
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
933
- const table = await this.lanceClient.openTable(TABLE_MESSAGES);
934
- let query = table.query().where(`\`threadId\` = '${threadId}'`);
935
-
936
- // Apply selectBy filters if provided
937
- if (selectBy) {
938
- // Handle 'include' to fetch specific messages
939
- if (selectBy.include && selectBy.include.length > 0) {
940
- const includeIds = selectBy.include.map(item => item.id);
941
- // Add additional query to include specific message IDs
942
- // This will be combined with the threadId filter
943
- const includeClause = includeIds.map(id => `\`id\` = '${id}'`).join(' OR ');
944
- query = query.where(`(\`threadId\` = '${threadId}' OR (${includeClause}))`);
945
-
946
- // Note: The surrounding messages (withPreviousMessages/withNextMessages) will be
947
- // handled after we retrieve the results
948
- }
949
- }
950
-
951
- // Fetch all records matching the query
952
- let records = await query.toArray();
953
-
954
- // Sort the records chronologically
955
- records.sort((a, b) => {
956
- const dateA = new Date(a.createdAt).getTime();
957
- const dateB = new Date(b.createdAt).getTime();
958
- return dateA - dateB; // Ascending order
959
- });
960
-
961
- // Process the include.withPreviousMessages and include.withNextMessages if specified
962
- if (selectBy?.include && selectBy.include.length > 0) {
963
- records = this.processMessagesWithContext(records, selectBy.include);
964
- }
965
-
966
- // If we're fetching the last N messages, take only the last N after sorting
967
- if (limit !== Number.MAX_SAFE_INTEGER) {
968
- records = records.slice(-limit);
969
- }
970
-
971
- const messages = this.processResultWithTypeConversion(records, await this.getTableSchema(TABLE_MESSAGES));
972
- const normalized = messages.map((msg: MastraMessageV2 | MastraMessageV1) => ({
973
- ...msg,
974
- content:
975
- typeof msg.content === 'string'
976
- ? (() => {
977
- try {
978
- return JSON.parse(msg.content);
979
- } catch {
980
- return msg.content;
981
- }
982
- })()
983
- : msg.content,
984
- }));
985
- const list = new MessageList({ threadId, resourceId }).add(normalized, 'memory');
986
- if (format === 'v2') return list.get.all.v2();
987
- return list.get.all.v1();
988
- } catch (error: any) {
989
- throw new MastraError(
990
- {
991
- id: 'LANCE_STORE_GET_MESSAGES_FAILED',
992
- domain: ErrorDomain.STORAGE,
993
- category: ErrorCategory.THIRD_PARTY,
994
- },
995
- error,
996
- );
997
- }
290
+ return this.stores.memory.getMessages({ threadId, resourceId, selectBy, format, threadConfig });
998
291
  }
999
292
 
1000
293
  async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
@@ -1002,237 +295,65 @@ export class LanceStorage extends MastraStorage {
1002
295
  async saveMessages(
1003
296
  args: { messages: MastraMessageV1[]; format?: undefined | 'v1' } | { messages: MastraMessageV2[]; format: 'v2' },
1004
297
  ): Promise<MastraMessageV2[] | MastraMessageV1[]> {
1005
- try {
1006
- const { messages, format = 'v1' } = args;
1007
- if (messages.length === 0) {
1008
- return [];
1009
- }
1010
-
1011
- const threadId = messages[0]?.threadId;
1012
-
1013
- if (!threadId) {
1014
- throw new Error('Thread ID is required');
1015
- }
1016
-
1017
- const transformedMessages = messages.map((message: MastraMessageV2 | MastraMessageV1) => ({
1018
- ...message,
1019
- content: JSON.stringify(message.content),
1020
- }));
1021
-
1022
- const table = await this.lanceClient.openTable(TABLE_MESSAGES);
1023
- await table.mergeInsert('id').whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
298
+ return this.stores.memory.saveMessages(args);
299
+ }
1024
300
 
1025
- const list = new MessageList().add(messages, 'memory');
1026
- if (format === `v2`) return list.get.all.v2();
1027
- return list.get.all.v1();
1028
- } catch (error: any) {
1029
- throw new MastraError(
1030
- {
1031
- id: 'LANCE_STORE_SAVE_MESSAGES_FAILED',
1032
- domain: ErrorDomain.STORAGE,
1033
- category: ErrorCategory.THIRD_PARTY,
1034
- },
1035
- error,
1036
- );
1037
- }
301
+ async getThreadsByResourceIdPaginated(args: {
302
+ resourceId: string;
303
+ page: number;
304
+ perPage: number;
305
+ }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
306
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
1038
307
  }
1039
308
 
1040
- async saveTrace({ trace }: { trace: TraceType }): Promise<TraceType> {
1041
- try {
1042
- const table = await this.lanceClient.openTable(TABLE_TRACES);
1043
- const record = {
1044
- ...trace,
1045
- attributes: JSON.stringify(trace.attributes),
1046
- status: JSON.stringify(trace.status),
1047
- events: JSON.stringify(trace.events),
1048
- links: JSON.stringify(trace.links),
1049
- other: JSON.stringify(trace.other),
1050
- };
1051
- await table.add([record], { mode: 'append' });
309
+ async getMessagesPaginated(
310
+ args: StorageGetMessagesArg & { format?: 'v1' | 'v2' },
311
+ ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
312
+ return this.stores.memory.getMessagesPaginated(args);
313
+ }
1052
314
 
1053
- return trace;
1054
- } catch (error: any) {
1055
- throw new MastraError(
1056
- {
1057
- id: 'LANCE_STORE_SAVE_TRACE_FAILED',
1058
- domain: ErrorDomain.STORAGE,
1059
- category: ErrorCategory.THIRD_PARTY,
1060
- },
1061
- error,
1062
- );
1063
- }
315
+ async updateMessages(_args: {
316
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
317
+ {
318
+ id: string;
319
+ content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
320
+ }[];
321
+ }): Promise<MastraMessageV2[]> {
322
+ return this.stores.memory.updateMessages(_args);
1064
323
  }
1065
324
 
1066
- async getTraceById({ traceId }: { traceId: string }): Promise<TraceType> {
1067
- try {
1068
- const table = await this.lanceClient.openTable(TABLE_TRACES);
1069
- const query = table.query().where(`id = '${traceId}'`);
1070
- const records = await query.toArray();
1071
- return this.processResultWithTypeConversion(records[0], await this.getTableSchema(TABLE_TRACES)) as TraceType;
1072
- } catch (error: any) {
1073
- throw new MastraError(
1074
- {
1075
- id: 'LANCE_STORE_GET_TRACE_BY_ID_FAILED',
1076
- domain: ErrorDomain.STORAGE,
1077
- category: ErrorCategory.THIRD_PARTY,
1078
- },
1079
- error,
1080
- );
1081
- }
325
+ async getTraceById(args: { traceId: string }): Promise<TraceType> {
326
+ return (this.stores as any).traces.getTraceById(args);
1082
327
  }
1083
328
 
1084
- async getTraces({
1085
- name,
1086
- scope,
1087
- page = 1,
1088
- perPage = 10,
1089
- attributes,
1090
- }: {
329
+ async getTraces(args: {
1091
330
  name?: string;
1092
331
  scope?: string;
1093
332
  page: number;
1094
333
  perPage: number;
1095
334
  attributes?: Record<string, string>;
1096
- }): Promise<TraceType[]> {
1097
- try {
1098
- const table = await this.lanceClient.openTable(TABLE_TRACES);
1099
- const query = table.query();
1100
-
1101
- if (name) {
1102
- query.where(`name = '${name}'`);
1103
- }
1104
-
1105
- if (scope) {
1106
- query.where(`scope = '${scope}'`);
1107
- }
1108
-
1109
- if (attributes) {
1110
- query.where(`attributes = '${JSON.stringify(attributes)}'`);
1111
- }
1112
-
1113
- // Calculate offset based on page and perPage
1114
- const offset = (page - 1) * perPage;
1115
-
1116
- // Apply limit for pagination
1117
- query.limit(perPage);
1118
-
1119
- // Apply offset if greater than 0
1120
- if (offset > 0) {
1121
- query.offset(offset);
1122
- }
1123
-
1124
- const records = await query.toArray();
1125
- return records.map(record => {
1126
- return {
1127
- ...record,
1128
- attributes: JSON.parse(record.attributes),
1129
- status: JSON.parse(record.status),
1130
- events: JSON.parse(record.events),
1131
- links: JSON.parse(record.links),
1132
- other: JSON.parse(record.other),
1133
- startTime: new Date(record.startTime),
1134
- endTime: new Date(record.endTime),
1135
- createdAt: new Date(record.createdAt),
1136
- };
1137
- }) as TraceType[];
1138
- } catch (error: any) {
1139
- throw new MastraError(
1140
- {
1141
- id: 'LANCE_STORE_GET_TRACES_FAILED',
1142
- domain: ErrorDomain.STORAGE,
1143
- category: ErrorCategory.THIRD_PARTY,
1144
- details: { name: name ?? '', scope: scope ?? '' },
1145
- },
1146
- error,
1147
- );
1148
- }
335
+ }): Promise<Trace[]> {
336
+ return (this.stores as any).traces.getTraces(args);
1149
337
  }
1150
338
 
1151
- async saveEvals({ evals }: { evals: EvalRow[] }): Promise<EvalRow[]> {
1152
- try {
1153
- const table = await this.lanceClient.openTable(TABLE_EVALS);
1154
- const transformedEvals = evals.map(evalRecord => ({
1155
- input: evalRecord.input,
1156
- output: evalRecord.output,
1157
- agent_name: evalRecord.agentName,
1158
- metric_name: evalRecord.metricName,
1159
- result: JSON.stringify(evalRecord.result),
1160
- instructions: evalRecord.instructions,
1161
- test_info: JSON.stringify(evalRecord.testInfo),
1162
- global_run_id: evalRecord.globalRunId,
1163
- run_id: evalRecord.runId,
1164
- created_at: new Date(evalRecord.createdAt).getTime(),
1165
- }));
1166
-
1167
- await table.add(transformedEvals, { mode: 'append' });
1168
- return evals;
1169
- } catch (error: any) {
1170
- throw new MastraError(
1171
- {
1172
- id: 'LANCE_STORE_SAVE_EVALS_FAILED',
1173
- domain: ErrorDomain.STORAGE,
1174
- category: ErrorCategory.THIRD_PARTY,
1175
- },
1176
- error,
1177
- );
1178
- }
339
+ async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
340
+ return (this.stores as any).traces.getTracesPaginated(args);
1179
341
  }
1180
342
 
1181
343
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
1182
- try {
1183
- if (type) {
1184
- this.logger.warn('Type is not implemented yet in LanceDB storage');
1185
- }
1186
- const table = await this.lanceClient.openTable(TABLE_EVALS);
1187
- const query = table.query().where(`agent_name = '${agentName}'`);
1188
- const records = await query.toArray();
1189
- return records.map(record => {
1190
- return {
1191
- id: record.id,
1192
- input: record.input,
1193
- output: record.output,
1194
- agentName: record.agent_name,
1195
- metricName: record.metric_name,
1196
- result: JSON.parse(record.result),
1197
- instructions: record.instructions,
1198
- testInfo: JSON.parse(record.test_info),
1199
- globalRunId: record.global_run_id,
1200
- runId: record.run_id,
1201
- createdAt: new Date(record.created_at).toString(),
1202
- };
1203
- }) as EvalRow[];
1204
- } catch (error: any) {
1205
- throw new MastraError(
1206
- {
1207
- id: 'LANCE_STORE_GET_EVALS_BY_AGENT_NAME_FAILED',
1208
- domain: ErrorDomain.STORAGE,
1209
- category: ErrorCategory.THIRD_PARTY,
1210
- details: { agentName },
1211
- },
1212
- error,
1213
- );
1214
- }
344
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
1215
345
  }
1216
346
 
1217
- private parseWorkflowRun(row: any): WorkflowRun {
1218
- let parsedSnapshot: WorkflowRunState | string = row.snapshot as string;
1219
- if (typeof parsedSnapshot === 'string') {
1220
- try {
1221
- parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
1222
- } catch (e) {
1223
- // If parsing fails, return the raw snapshot string
1224
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1225
- }
1226
- }
1227
-
1228
- return {
1229
- workflowName: row.workflow_name,
1230
- runId: row.run_id,
1231
- snapshot: parsedSnapshot,
1232
- createdAt: this.ensureDate(row.createdAt)!,
1233
- updatedAt: this.ensureDate(row.updatedAt)!,
1234
- resourceId: row.resourceId,
1235
- };
347
+ async getEvals(options: {
348
+ agentName?: string;
349
+ type?: 'test' | 'live';
350
+ page?: number;
351
+ perPage?: number;
352
+ fromDate?: Date;
353
+ toDate?: Date;
354
+ dateRange?: { start?: Date; end?: Date };
355
+ }): Promise<PaginationInfo & { evals: EvalRow[] }> {
356
+ return this.stores.legacyEvals.getEvals(options);
1236
357
  }
1237
358
 
1238
359
  async getWorkflowRuns(args?: {
@@ -1243,53 +364,9 @@ export class LanceStorage extends MastraStorage {
1243
364
  limit?: number;
1244
365
  offset?: number;
1245
366
  }): Promise<WorkflowRuns> {
1246
- try {
1247
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
1248
- const query = table.query();
1249
-
1250
- if (args?.workflowName) {
1251
- query.where(`workflow_name = '${args.workflowName}'`);
1252
- }
1253
-
1254
- if (args?.fromDate) {
1255
- query.where(`\`createdAt\` >= ${args.fromDate.getTime()}`);
1256
- }
1257
-
1258
- if (args?.toDate) {
1259
- query.where(`\`createdAt\` <= ${args.toDate.getTime()}`);
1260
- }
1261
-
1262
- if (args?.limit) {
1263
- query.limit(args.limit);
1264
- }
1265
-
1266
- if (args?.offset) {
1267
- query.offset(args.offset);
1268
- }
1269
-
1270
- const records = await query.toArray();
1271
- return {
1272
- runs: records.map(record => this.parseWorkflowRun(record)),
1273
- total: records.length,
1274
- };
1275
- } catch (error: any) {
1276
- throw new MastraError(
1277
- {
1278
- id: 'LANCE_STORE_GET_WORKFLOW_RUNS_FAILED',
1279
- domain: ErrorDomain.STORAGE,
1280
- category: ErrorCategory.THIRD_PARTY,
1281
- details: { namespace: args?.namespace ?? '', workflowName: args?.workflowName ?? '' },
1282
- },
1283
- error,
1284
- );
1285
- }
367
+ return this.stores.workflows.getWorkflowRuns(args);
1286
368
  }
1287
369
 
1288
- /**
1289
- * Retrieve a single workflow run by its runId.
1290
- * @param args The ID of the workflow run to retrieve
1291
- * @returns The workflow run object or null if not found
1292
- */
1293
370
  async getWorkflowRunById(args: { runId: string; workflowName?: string }): Promise<{
1294
371
  workflowName: string;
1295
372
  runId: string;
@@ -1297,28 +374,7 @@ export class LanceStorage extends MastraStorage {
1297
374
  createdAt: Date;
1298
375
  updatedAt: Date;
1299
376
  } | null> {
1300
- try {
1301
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
1302
- let whereClause = `run_id = '${args.runId}'`;
1303
- if (args.workflowName) {
1304
- whereClause += ` AND workflow_name = '${args.workflowName}'`;
1305
- }
1306
- const query = table.query().where(whereClause);
1307
- const records = await query.toArray();
1308
- if (records.length === 0) return null;
1309
- const record = records[0];
1310
- return this.parseWorkflowRun(record);
1311
- } catch (error: any) {
1312
- throw new MastraError(
1313
- {
1314
- id: 'LANCE_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED',
1315
- domain: ErrorDomain.STORAGE,
1316
- category: ErrorCategory.THIRD_PARTY,
1317
- details: { runId: args.runId, workflowName: args.workflowName ?? '' },
1318
- },
1319
- error,
1320
- );
1321
- }
377
+ return this.stores.workflows.getWorkflowRunById(args);
1322
378
  }
1323
379
 
1324
380
  async persistWorkflowSnapshot({
@@ -1330,46 +386,9 @@ export class LanceStorage extends MastraStorage {
1330
386
  runId: string;
1331
387
  snapshot: WorkflowRunState;
1332
388
  }): Promise<void> {
1333
- try {
1334
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
1335
-
1336
- // Try to find the existing record
1337
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
1338
- const records = await query.toArray();
1339
- let createdAt: number;
1340
- const now = Date.now();
1341
-
1342
- if (records.length > 0) {
1343
- createdAt = records[0].createdAt ?? now;
1344
- } else {
1345
- createdAt = now;
1346
- }
1347
-
1348
- const record = {
1349
- workflow_name: workflowName,
1350
- run_id: runId,
1351
- snapshot: JSON.stringify(snapshot),
1352
- createdAt,
1353
- updatedAt: now,
1354
- };
1355
-
1356
- await table
1357
- .mergeInsert(['workflow_name', 'run_id'])
1358
- .whenMatchedUpdateAll()
1359
- .whenNotMatchedInsertAll()
1360
- .execute([record]);
1361
- } catch (error: any) {
1362
- throw new MastraError(
1363
- {
1364
- id: 'LANCE_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
1365
- domain: ErrorDomain.STORAGE,
1366
- category: ErrorCategory.THIRD_PARTY,
1367
- details: { workflowName, runId },
1368
- },
1369
- error,
1370
- );
1371
- }
389
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
1372
390
  }
391
+
1373
392
  async loadWorkflowSnapshot({
1374
393
  workflowName,
1375
394
  runId,
@@ -1377,71 +396,46 @@ export class LanceStorage extends MastraStorage {
1377
396
  workflowName: string;
1378
397
  runId: string;
1379
398
  }): Promise<WorkflowRunState | null> {
1380
- try {
1381
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
1382
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
1383
- const records = await query.toArray();
1384
- return records.length > 0 ? JSON.parse(records[0].snapshot) : null;
1385
- } catch (error: any) {
1386
- throw new MastraError(
1387
- {
1388
- id: 'LANCE_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
1389
- domain: ErrorDomain.STORAGE,
1390
- category: ErrorCategory.THIRD_PARTY,
1391
- details: { workflowName, runId },
1392
- },
1393
- error,
1394
- );
1395
- }
399
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
1396
400
  }
1397
401
 
1398
- async getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & { traces: Trace[] }> {
1399
- throw new MastraError(
1400
- {
1401
- id: 'LANCE_STORE_GET_TRACES_PAGINATED_FAILED',
1402
- domain: ErrorDomain.STORAGE,
1403
- category: ErrorCategory.THIRD_PARTY,
1404
- },
1405
- 'Method not implemented.',
1406
- );
402
+ async getScoreById({ id: _id }: { id: string }): Promise<ScoreRowData | null> {
403
+ return this.stores.scores.getScoreById({ id: _id });
1407
404
  }
1408
405
 
1409
- async getThreadsByResourceIdPaginated(_args: {
1410
- resourceId: string;
1411
- page?: number;
1412
- perPage?: number;
1413
- }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
1414
- throw new MastraError(
1415
- {
1416
- id: 'LANCE_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED',
1417
- domain: ErrorDomain.STORAGE,
1418
- category: ErrorCategory.THIRD_PARTY,
1419
- },
1420
- 'Method not implemented.',
1421
- );
406
+ async getScoresByScorerId({
407
+ scorerId,
408
+ pagination,
409
+ }: {
410
+ scorerId: string;
411
+ pagination: StoragePagination;
412
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
413
+ return this.stores.scores.getScoresByScorerId({ scorerId, pagination });
1422
414
  }
1423
415
 
1424
- async getMessagesPaginated(
1425
- _args: StorageGetMessagesArg,
1426
- ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
1427
- throw new MastraError(
1428
- {
1429
- id: 'LANCE_STORE_GET_MESSAGES_PAGINATED_FAILED',
1430
- domain: ErrorDomain.STORAGE,
1431
- category: ErrorCategory.THIRD_PARTY,
1432
- },
1433
- 'Method not implemented.',
1434
- );
416
+ async saveScore(_score: ScoreRowData): Promise<{ score: ScoreRowData }> {
417
+ return this.stores.scores.saveScore(_score);
1435
418
  }
1436
419
 
1437
- async updateMessages(_args: {
1438
- messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
1439
- {
1440
- id: string;
1441
- content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
1442
- }[];
1443
- }): Promise<MastraMessageV2[]> {
1444
- this.logger.error('updateMessages is not yet implemented in LanceStore');
1445
- throw new Error('Method not implemented');
420
+ async getScoresByRunId({
421
+ runId,
422
+ pagination,
423
+ }: {
424
+ runId: string;
425
+ pagination: StoragePagination;
426
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
427
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
428
+ }
429
+
430
+ async getScoresByEntityId({
431
+ entityId,
432
+ entityType,
433
+ pagination,
434
+ }: {
435
+ pagination: StoragePagination;
436
+ entityId: string;
437
+ entityType: string;
438
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
439
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
1446
440
  }
1447
441
  }