@mastra/lance 0.0.0-working-memory-per-user-20250620161509 → 0.0.0-zod-v4-compat-part-2-20250820135355

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +170 -2
  2. package/LICENSE.md +11 -42
  3. package/dist/index.cjs +2321 -695
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +2286 -660
  8. package/dist/index.js.map +1 -0
  9. package/dist/storage/domains/legacy-evals/index.d.ts +25 -0
  10. package/dist/storage/domains/legacy-evals/index.d.ts.map +1 -0
  11. package/dist/storage/domains/memory/index.d.ts +94 -0
  12. package/dist/storage/domains/memory/index.d.ts.map +1 -0
  13. package/dist/storage/domains/operations/index.d.ts +40 -0
  14. package/dist/storage/domains/operations/index.d.ts.map +1 -0
  15. package/dist/storage/domains/scores/index.d.ts +39 -0
  16. package/dist/storage/domains/scores/index.d.ts.map +1 -0
  17. package/dist/storage/domains/traces/index.d.ts +34 -0
  18. package/dist/storage/domains/traces/index.d.ts.map +1 -0
  19. package/dist/storage/domains/utils.d.ts +10 -0
  20. package/dist/storage/domains/utils.d.ts.map +1 -0
  21. package/dist/storage/domains/workflows/index.d.ts +38 -0
  22. package/dist/storage/domains/workflows/index.d.ts.map +1 -0
  23. package/dist/storage/index.d.ts +233 -0
  24. package/dist/storage/index.d.ts.map +1 -0
  25. package/dist/vector/filter.d.ts +41 -0
  26. package/dist/vector/filter.d.ts.map +1 -0
  27. package/dist/vector/index.d.ts +85 -0
  28. package/dist/vector/index.d.ts.map +1 -0
  29. package/dist/vector/types.d.ts +15 -0
  30. package/dist/vector/types.d.ts.map +1 -0
  31. package/package.json +9 -9
  32. package/src/storage/domains/legacy-evals/index.ts +156 -0
  33. package/src/storage/domains/memory/index.ts +947 -0
  34. package/src/storage/domains/operations/index.ts +489 -0
  35. package/src/storage/domains/scores/index.ts +221 -0
  36. package/src/storage/domains/traces/index.ts +212 -0
  37. package/src/storage/domains/utils.ts +158 -0
  38. package/src/storage/domains/workflows/index.ts +207 -0
  39. package/src/storage/index.test.ts +6 -1262
  40. package/src/storage/index.ts +168 -755
  41. package/src/vector/filter.test.ts +3 -3
  42. package/src/vector/filter.ts +24 -4
  43. package/src/vector/index.test.ts +3 -3
  44. package/src/vector/index.ts +320 -79
  45. package/tsconfig.build.json +9 -0
  46. package/tsconfig.json +1 -1
  47. package/tsup.config.ts +22 -0
  48. package/dist/_tsup-dts-rollup.d.cts +0 -395
  49. package/dist/_tsup-dts-rollup.d.ts +0 -395
  50. package/dist/index.d.cts +0 -2
@@ -1,34 +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';
4
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
5
5
  import type { MastraMessageV1, MastraMessageV2, StorageThreadType, TraceType } from '@mastra/core/memory';
6
- import {
7
- MastraStorage,
8
- TABLE_EVALS,
9
- TABLE_MESSAGES,
10
- TABLE_THREADS,
11
- TABLE_TRACES,
12
- TABLE_WORKFLOW_SNAPSHOT,
13
- } from '@mastra/core/storage';
6
+ import type { ScoreRowData } from '@mastra/core/scores';
7
+ import { MastraStorage } from '@mastra/core/storage';
14
8
  import type {
15
9
  TABLE_NAMES,
16
10
  PaginationInfo,
17
11
  StorageGetMessagesArg,
18
- StorageGetTracesArg,
19
12
  StorageColumn,
20
13
  EvalRow,
21
- WorkflowRun,
22
14
  WorkflowRuns,
15
+ StoragePagination,
16
+ StorageDomains,
17
+ StorageGetTracesPaginatedArg,
18
+ StorageResourceType,
23
19
  } from '@mastra/core/storage';
24
20
  import type { Trace } from '@mastra/core/telemetry';
25
21
  import type { WorkflowRunState } from '@mastra/core/workflows';
26
- import type { DataType } from 'apache-arrow';
27
- 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';
28
28
 
29
29
  export class LanceStorage extends MastraStorage {
30
+ stores: StorageDomains;
30
31
  private lanceClient!: Connection;
31
-
32
32
  /**
33
33
  * Creates a new instance of LanceStorage
34
34
  * @param uri The URI to connect to LanceDB
@@ -55,9 +55,27 @@ export class LanceStorage extends MastraStorage {
55
55
  const instance = new LanceStorage(name);
56
56
  try {
57
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
+ };
58
67
  return instance;
59
68
  } catch (e: any) {
60
- throw new Error(`Failed to connect to LanceDB: ${e}`);
69
+ throw new MastraError(
70
+ {
71
+ id: 'STORAGE_LANCE_STORAGE_CONNECT_FAILED',
72
+ domain: ErrorDomain.STORAGE,
73
+ category: ErrorCategory.THIRD_PARTY,
74
+ text: `Failed to connect to LanceDB: ${e.message || e}`,
75
+ details: { uri, optionsProvided: !!options },
76
+ },
77
+ e,
78
+ );
61
79
  }
62
80
  }
63
81
 
@@ -67,6 +85,16 @@ export class LanceStorage extends MastraStorage {
67
85
  */
68
86
  private constructor(name: string) {
69
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
+ };
70
98
  }
71
99
 
72
100
  async createTable({
@@ -76,405 +104,47 @@ export class LanceStorage extends MastraStorage {
76
104
  tableName: TABLE_NAMES;
77
105
  schema: Record<string, StorageColumn>;
78
106
  }): Promise<void> {
79
- try {
80
- const arrowSchema = this.translateSchema(schema);
81
- await this.lanceClient.createEmptyTable(tableName, arrowSchema);
82
- } catch (error: any) {
83
- throw new Error(`Failed to create table: ${error}`);
84
- }
85
- }
86
-
87
- private translateSchema(schema: Record<string, StorageColumn>): Schema {
88
- const fields = Object.entries(schema).map(([name, column]) => {
89
- // Convert string type to Arrow DataType
90
- let arrowType: DataType;
91
- switch (column.type.toLowerCase()) {
92
- case 'text':
93
- case 'uuid':
94
- arrowType = new Utf8();
95
- break;
96
- case 'int':
97
- case 'integer':
98
- arrowType = new Int32();
99
- break;
100
- case 'bigint':
101
- arrowType = new Float64();
102
- break;
103
- case 'float':
104
- arrowType = new Float32();
105
- break;
106
- case 'jsonb':
107
- case 'json':
108
- arrowType = new Utf8();
109
- break;
110
- case 'binary':
111
- arrowType = new Binary();
112
- break;
113
- case 'timestamp':
114
- arrowType = new Float64();
115
- break;
116
- default:
117
- // Default to string for unknown types
118
- arrowType = new Utf8();
119
- }
120
-
121
- // Create a field with the appropriate arrow type
122
- return new Field(name, arrowType, column.nullable ?? true);
123
- });
124
-
125
- return new Schema(fields);
126
- }
127
-
128
- /**
129
- * Drop a table if it exists
130
- * @param tableName Name of the table to drop
131
- */
132
- async dropTable(tableName: TABLE_NAMES): Promise<void> {
133
- try {
134
- await this.lanceClient.dropTable(tableName);
135
- } catch (error: any) {
136
- // Don't throw if the table doesn't exist
137
- if (error.toString().includes('was not found')) {
138
- this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
139
- return;
140
- }
141
- throw new Error(`Failed to drop table: ${error}`);
142
- }
143
- }
144
-
145
- /**
146
- * Get table schema
147
- * @param tableName Name of the table
148
- * @returns Table schema
149
- */
150
- async getTableSchema(tableName: TABLE_NAMES): Promise<SchemaLike> {
151
- try {
152
- const table = await this.lanceClient.openTable(tableName);
153
- const rawSchema = await table.schema();
154
- const fields = rawSchema.fields as FieldLike[];
155
-
156
- // Convert schema to SchemaLike format
157
- return {
158
- fields,
159
- metadata: new Map<string, string>(),
160
- get names() {
161
- return fields.map((field: FieldLike) => field.name);
162
- },
163
- };
164
- } catch (error: any) {
165
- throw new Error(`Failed to get table schema: ${error}`);
166
- }
107
+ return this.stores.operations.createTable({ tableName, schema });
167
108
  }
168
109
 
169
- protected getDefaultValue(type: StorageColumn['type']): string {
170
- switch (type) {
171
- case 'text':
172
- return "''";
173
- case 'timestamp':
174
- return 'CURRENT_TIMESTAMP';
175
- case 'integer':
176
- case 'bigint':
177
- return '0';
178
- case 'jsonb':
179
- return "'{}'";
180
- case 'uuid':
181
- return "''";
182
- default:
183
- return super.getDefaultValue(type);
184
- }
110
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
111
+ return this.stores.operations.dropTable({ tableName });
185
112
  }
186
113
 
187
- /**
188
- * Alters table schema to add columns if they don't exist
189
- * @param tableName Name of the table
190
- * @param schema Schema of the table
191
- * @param ifNotExists Array of column names to add if they don't exist
192
- */
193
114
  async alterTable({
194
115
  tableName,
195
116
  schema,
196
117
  ifNotExists,
197
118
  }: {
198
- tableName: string;
119
+ tableName: TABLE_NAMES;
199
120
  schema: Record<string, StorageColumn>;
200
121
  ifNotExists: string[];
201
122
  }): Promise<void> {
202
- const table = await this.lanceClient.openTable(tableName);
203
- const currentSchema = await table.schema();
204
- const existingFields = new Set(currentSchema.fields.map((f: any) => f.name));
205
-
206
- const typeMap: Record<string, string> = {
207
- text: 'string',
208
- integer: 'int',
209
- bigint: 'bigint',
210
- timestamp: 'timestamp',
211
- jsonb: 'string',
212
- uuid: 'string',
213
- };
214
-
215
- // Find columns to add
216
- const columnsToAdd = ifNotExists
217
- .filter(col => schema[col] && !existingFields.has(col))
218
- .map(col => {
219
- const colDef = schema[col];
220
- return {
221
- name: col,
222
- valueSql: colDef?.nullable
223
- ? `cast(NULL as ${typeMap[colDef.type ?? 'text']})`
224
- : `cast(${this.getDefaultValue(colDef?.type ?? 'text')} as ${typeMap[colDef?.type ?? 'text']})`,
225
- };
226
- });
227
-
228
- if (columnsToAdd.length > 0) {
229
- await table.addColumns(columnsToAdd);
230
- this.logger?.info?.(`Added columns [${columnsToAdd.map(c => c.name).join(', ')}] to table ${tableName}`);
231
- }
123
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
232
124
  }
233
125
 
234
126
  async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
235
- const table = await this.lanceClient.openTable(tableName);
236
-
237
- // delete function always takes a predicate as an argument, so we use '1=1' to delete all records because it is always true.
238
- await table.delete('1=1');
127
+ return this.stores.operations.clearTable({ tableName });
239
128
  }
240
129
 
241
- /**
242
- * 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.
243
- * @param tableName The name of the table to insert into.
244
- * @param record The record to insert.
245
- */
246
- async insert({ tableName, record }: { tableName: string; record: Record<string, any> }): Promise<void> {
247
- try {
248
- const table = await this.lanceClient.openTable(tableName);
249
-
250
- const processedRecord = { ...record };
251
-
252
- for (const key in processedRecord) {
253
- if (
254
- processedRecord[key] !== null &&
255
- typeof processedRecord[key] === 'object' &&
256
- !(processedRecord[key] instanceof Date)
257
- ) {
258
- this.logger.debug('Converting object to JSON string: ', processedRecord[key]);
259
- processedRecord[key] = JSON.stringify(processedRecord[key]);
260
- }
261
- }
262
-
263
- await table.add([processedRecord], { mode: 'overwrite' });
264
- } catch (error: any) {
265
- throw new Error(`Failed to insert record: ${error}`);
266
- }
130
+ async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
131
+ return this.stores.operations.insert({ tableName, record });
267
132
  }
268
133
 
269
- /**
270
- * 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.
271
- * @param tableName The name of the table to insert into.
272
- * @param records The records to insert.
273
- */
274
- async batchInsert({ tableName, records }: { tableName: string; records: Record<string, any>[] }): Promise<void> {
275
- try {
276
- const table = await this.lanceClient.openTable(tableName);
277
-
278
- const processedRecords = records.map(record => {
279
- const processedRecord = { ...record };
280
-
281
- // Convert values based on schema type
282
- for (const key in processedRecord) {
283
- // Skip null/undefined values
284
- if (processedRecord[key] == null) continue;
285
-
286
- if (
287
- processedRecord[key] !== null &&
288
- typeof processedRecord[key] === 'object' &&
289
- !(processedRecord[key] instanceof Date)
290
- ) {
291
- processedRecord[key] = JSON.stringify(processedRecord[key]);
292
- }
293
- }
294
-
295
- return processedRecord;
296
- });
297
-
298
- await table.add(processedRecords, { mode: 'overwrite' });
299
- } catch (error: any) {
300
- throw new Error(`Failed to batch insert records: ${error}`);
301
- }
134
+ async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
135
+ return this.stores.operations.batchInsert({ tableName, records });
302
136
  }
303
137
 
304
- /**
305
- * Load a record from the database by its key(s)
306
- * @param tableName The name of the table to query
307
- * @param keys Record of key-value pairs to use for lookup
308
- * @throws Error if invalid types are provided for keys
309
- * @returns The loaded record with proper type conversions, or null if not found
310
- */
311
138
  async load({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, any> }): Promise<any> {
312
- try {
313
- const table = await this.lanceClient.openTable(tableName);
314
- const tableSchema = await this.getTableSchema(tableName);
315
- const query = table.query();
316
-
317
- // Build filter condition with 'and' between all conditions
318
- if (Object.keys(keys).length > 0) {
319
- // Validate key types against schema
320
- this.validateKeyTypes(keys, tableSchema);
321
-
322
- const filterConditions = Object.entries(keys)
323
- .map(([key, value]) => {
324
- // Check if key is in camelCase and wrap it in backticks if it is
325
- const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
326
- const quotedKey = isCamelCase ? `\`${key}\`` : key;
327
-
328
- // Handle different types appropriately
329
- if (typeof value === 'string') {
330
- return `${quotedKey} = '${value}'`;
331
- } else if (value === null) {
332
- return `${quotedKey} IS NULL`;
333
- } else {
334
- // For numbers, booleans, etc.
335
- return `${quotedKey} = ${value}`;
336
- }
337
- })
338
- .join(' AND ');
339
-
340
- this.logger.debug('where clause generated: ' + filterConditions);
341
- query.where(filterConditions);
342
- }
343
-
344
- const result = await query.limit(1).toArray();
345
-
346
- if (result.length === 0) {
347
- this.logger.debug('No record found');
348
- return null;
349
- }
350
-
351
- // Process the result with type conversions
352
- return this.processResultWithTypeConversion(result[0], tableSchema);
353
- } catch (error: any) {
354
- throw new Error(`Failed to load record: ${error}`);
355
- }
139
+ return this.stores.operations.load({ tableName, keys });
356
140
  }
357
141
 
358
- /**
359
- * Validates that key types match the schema definition
360
- * @param keys The keys to validate
361
- * @param tableSchema The table schema to validate against
362
- * @throws Error if a key has an incompatible type
363
- */
364
- private validateKeyTypes(keys: Record<string, any>, tableSchema: SchemaLike): void {
365
- // Create a map of field names to their expected types
366
- const fieldTypes = new Map(
367
- tableSchema.fields.map((field: any) => [field.name, field.type?.toString().toLowerCase()]),
368
- );
369
-
370
- for (const [key, value] of Object.entries(keys)) {
371
- const fieldType = fieldTypes.get(key);
372
-
373
- if (!fieldType) {
374
- throw new Error(`Field '${key}' does not exist in table schema`);
375
- }
376
-
377
- // Type validation
378
- if (value !== null) {
379
- if ((fieldType.includes('int') || fieldType.includes('bigint')) && typeof value !== 'number') {
380
- throw new Error(`Expected numeric value for field '${key}', got ${typeof value}`);
381
- }
382
-
383
- if (fieldType.includes('utf8') && typeof value !== 'string') {
384
- throw new Error(`Expected string value for field '${key}', got ${typeof value}`);
385
- }
386
-
387
- if (fieldType.includes('timestamp') && !(value instanceof Date) && typeof value !== 'string') {
388
- throw new Error(`Expected Date or string value for field '${key}', got ${typeof value}`);
389
- }
390
- }
391
- }
392
- }
393
-
394
- /**
395
- * Process a database result with appropriate type conversions based on the table schema
396
- * @param rawResult The raw result object from the database
397
- * @param tableSchema The schema of the table containing type information
398
- * @returns Processed result with correct data types
399
- */
400
- private processResultWithTypeConversion(
401
- rawResult: Record<string, any> | Record<string, any>[],
402
- tableSchema: SchemaLike,
403
- ): Record<string, any> | Record<string, any>[] {
404
- // Build a map of field names to their schema types
405
- const fieldTypeMap = new Map();
406
- tableSchema.fields.forEach((field: any) => {
407
- const fieldName = field.name;
408
- const fieldTypeStr = field.type.toString().toLowerCase();
409
- fieldTypeMap.set(fieldName, fieldTypeStr);
410
- });
411
-
412
- // Handle array case
413
- if (Array.isArray(rawResult)) {
414
- return rawResult.map(item => this.processResultWithTypeConversion(item, tableSchema));
415
- }
416
-
417
- // Handle single record case
418
- const processedResult = { ...rawResult };
419
-
420
- // Convert each field according to its schema type
421
- for (const key in processedResult) {
422
- const fieldTypeStr = fieldTypeMap.get(key);
423
- if (!fieldTypeStr) continue;
424
-
425
- // Skip conversion for ID fields - preserve their original format
426
- // if (key === 'id') {
427
- // continue;
428
- // }
429
-
430
- // Only try to convert string values
431
- if (typeof processedResult[key] === 'string') {
432
- // Numeric types
433
- if (fieldTypeStr.includes('int32') || fieldTypeStr.includes('float32')) {
434
- if (!isNaN(Number(processedResult[key]))) {
435
- processedResult[key] = Number(processedResult[key]);
436
- }
437
- } else if (fieldTypeStr.includes('int64')) {
438
- processedResult[key] = Number(processedResult[key]);
439
- } else if (fieldTypeStr.includes('utf8')) {
440
- try {
441
- processedResult[key] = JSON.parse(processedResult[key]);
442
- } catch (e) {
443
- // If JSON parsing fails, keep the original string
444
- this.logger.debug(`Failed to parse JSON for key ${key}: ${e}`);
445
- }
446
- }
447
- } else if (typeof processedResult[key] === 'bigint') {
448
- // Convert BigInt values to regular numbers for application layer
449
- processedResult[key] = Number(processedResult[key]);
450
- }
451
- }
452
-
453
- return processedResult;
454
- }
455
-
456
- getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
457
- try {
458
- return this.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
459
- } catch (error: any) {
460
- throw new Error(`Failed to get thread by ID: ${error}`);
461
- }
142
+ async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
143
+ return this.stores.memory.getThreadById({ threadId });
462
144
  }
463
145
 
464
146
  async getThreadsByResourceId({ resourceId }: { resourceId: string }): Promise<StorageThreadType[]> {
465
- try {
466
- const table = await this.lanceClient.openTable(TABLE_THREADS);
467
- // fetches all threads with the given resourceId
468
- const query = table.query().where(`\`resourceId\` = '${resourceId}'`);
469
-
470
- const records = await query.toArray();
471
- return this.processResultWithTypeConversion(
472
- records,
473
- await this.getTableSchema(TABLE_THREADS),
474
- ) as StorageThreadType[];
475
- } catch (error: any) {
476
- throw new Error(`Failed to get threads by resource ID: ${error}`);
477
- }
147
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
478
148
  }
479
149
 
480
150
  /**
@@ -483,15 +153,7 @@ export class LanceStorage extends MastraStorage {
483
153
  * @returns The saved thread
484
154
  */
485
155
  async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
486
- try {
487
- const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
488
- const table = await this.lanceClient.openTable(TABLE_THREADS);
489
- await table.add([record], { mode: 'append' });
490
-
491
- return thread;
492
- } catch (error: any) {
493
- throw new Error(`Failed to save thread: ${error}`);
494
- }
156
+ return this.stores.memory.saveThread({ thread });
495
157
  }
496
158
 
497
159
  async updateThread({
@@ -503,30 +165,41 @@ export class LanceStorage extends MastraStorage {
503
165
  title: string;
504
166
  metadata: Record<string, unknown>;
505
167
  }): Promise<StorageThreadType> {
506
- try {
507
- const record = { id, title, metadata: JSON.stringify(metadata) };
508
- const table = await this.lanceClient.openTable(TABLE_THREADS);
509
- await table.add([record], { mode: 'overwrite' });
168
+ return this.stores.memory.updateThread({ id, title, metadata });
169
+ }
510
170
 
511
- const query = table.query().where(`id = '${id}'`);
171
+ async deleteThread({ threadId }: { threadId: string }): Promise<void> {
172
+ return this.stores.memory.deleteThread({ threadId });
173
+ }
512
174
 
513
- const records = await query.toArray();
514
- return this.processResultWithTypeConversion(
515
- records[0],
516
- await this.getTableSchema(TABLE_THREADS),
517
- ) as StorageThreadType;
518
- } catch (error: any) {
519
- throw new Error(`Failed to update thread: ${error}`);
520
- }
175
+ public get supports() {
176
+ return {
177
+ selectByIncludeResourceScope: true,
178
+ resourceWorkingMemory: true,
179
+ hasColumn: true,
180
+ createTable: true,
181
+ deleteMessages: false,
182
+ };
521
183
  }
522
184
 
523
- async deleteThread({ threadId }: { threadId: string }): Promise<void> {
524
- try {
525
- const table = await this.lanceClient.openTable(TABLE_THREADS);
526
- await table.delete(`id = '${threadId}'`);
527
- } catch (error: any) {
528
- throw new Error(`Failed to delete thread: ${error}`);
529
- }
185
+ async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
186
+ return this.stores.memory.getResourceById({ resourceId });
187
+ }
188
+
189
+ async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
190
+ return this.stores.memory.saveResource({ resource });
191
+ }
192
+
193
+ async updateResource({
194
+ resourceId,
195
+ workingMemory,
196
+ metadata,
197
+ }: {
198
+ resourceId: string;
199
+ workingMemory?: string;
200
+ metadata?: Record<string, unknown>;
201
+ }): Promise<StorageResourceType> {
202
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
530
203
  }
531
204
 
532
205
  /**
@@ -556,6 +229,7 @@ export class LanceStorage extends MastraStorage {
556
229
 
557
230
  for (const item of messagesWithContext) {
558
231
  const messageIndex = messageIndexMap.get(item.id);
232
+
559
233
  if (messageIndex !== undefined) {
560
234
  // Add previous messages if requested
561
235
  if (item.withPreviousMessages) {
@@ -614,69 +288,7 @@ export class LanceStorage extends MastraStorage {
614
288
  format,
615
289
  threadConfig,
616
290
  }: StorageGetMessagesArg & { format?: 'v1' | 'v2' }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
617
- try {
618
- if (threadConfig) {
619
- throw new Error('ThreadConfig is not supported by LanceDB storage');
620
- }
621
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
622
- const table = await this.lanceClient.openTable(TABLE_MESSAGES);
623
- let query = table.query().where(`\`threadId\` = '${threadId}'`);
624
-
625
- // Apply selectBy filters if provided
626
- if (selectBy) {
627
- // Handle 'include' to fetch specific messages
628
- if (selectBy.include && selectBy.include.length > 0) {
629
- const includeIds = selectBy.include.map(item => item.id);
630
- // Add additional query to include specific message IDs
631
- // This will be combined with the threadId filter
632
- const includeClause = includeIds.map(id => `\`id\` = '${id}'`).join(' OR ');
633
- query = query.where(`(\`threadId\` = '${threadId}' OR (${includeClause}))`);
634
-
635
- // Note: The surrounding messages (withPreviousMessages/withNextMessages) will be
636
- // handled after we retrieve the results
637
- }
638
- }
639
-
640
- // Fetch all records matching the query
641
- let records = await query.toArray();
642
-
643
- // Sort the records chronologically
644
- records.sort((a, b) => {
645
- const dateA = new Date(a.createdAt).getTime();
646
- const dateB = new Date(b.createdAt).getTime();
647
- return dateA - dateB; // Ascending order
648
- });
649
-
650
- // Process the include.withPreviousMessages and include.withNextMessages if specified
651
- if (selectBy?.include && selectBy.include.length > 0) {
652
- records = this.processMessagesWithContext(records, selectBy.include);
653
- }
654
-
655
- // If we're fetching the last N messages, take only the last N after sorting
656
- if (limit !== Number.MAX_SAFE_INTEGER) {
657
- records = records.slice(-limit);
658
- }
659
-
660
- const messages = this.processResultWithTypeConversion(records, await this.getTableSchema(TABLE_MESSAGES));
661
- const normalized = messages.map((msg: MastraMessageV2 | MastraMessageV1) => ({
662
- ...msg,
663
- content:
664
- typeof msg.content === 'string'
665
- ? (() => {
666
- try {
667
- return JSON.parse(msg.content);
668
- } catch {
669
- return msg.content;
670
- }
671
- })()
672
- : msg.content,
673
- }));
674
- const list = new MessageList({ threadId, resourceId }).add(normalized, 'memory');
675
- if (format === 'v2') return list.get.all.v2();
676
- return list.get.all.v1();
677
- } catch (error: any) {
678
- throw new Error(`Failed to get messages: ${error}`);
679
- }
291
+ return this.stores.memory.getMessages({ threadId, resourceId, selectBy, format, threadConfig });
680
292
  }
681
293
 
682
294
  async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
@@ -684,192 +296,65 @@ export class LanceStorage extends MastraStorage {
684
296
  async saveMessages(
685
297
  args: { messages: MastraMessageV1[]; format?: undefined | 'v1' } | { messages: MastraMessageV2[]; format: 'v2' },
686
298
  ): Promise<MastraMessageV2[] | MastraMessageV1[]> {
687
- try {
688
- const { messages, format = 'v1' } = args;
689
- if (messages.length === 0) {
690
- return [];
691
- }
692
-
693
- const threadId = messages[0]?.threadId;
694
-
695
- if (!threadId) {
696
- throw new Error('Thread ID is required');
697
- }
698
-
699
- const transformedMessages = messages.map((message: MastraMessageV2 | MastraMessageV1) => ({
700
- ...message,
701
- content: JSON.stringify(message.content),
702
- }));
299
+ return this.stores.memory.saveMessages(args);
300
+ }
703
301
 
704
- const table = await this.lanceClient.openTable(TABLE_MESSAGES);
705
- await table.add(transformedMessages, { mode: 'overwrite' });
706
- const list = new MessageList().add(messages, 'memory');
707
- if (format === `v2`) return list.get.all.v2();
708
- return list.get.all.v1();
709
- } catch (error: any) {
710
- throw new Error(`Failed to save messages: ${error}`);
711
- }
302
+ async getThreadsByResourceIdPaginated(args: {
303
+ resourceId: string;
304
+ page: number;
305
+ perPage: number;
306
+ }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
307
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
712
308
  }
713
309
 
714
- async saveTrace({ trace }: { trace: TraceType }): Promise<TraceType> {
715
- try {
716
- const table = await this.lanceClient.openTable(TABLE_TRACES);
717
- const record = {
718
- ...trace,
719
- attributes: JSON.stringify(trace.attributes),
720
- status: JSON.stringify(trace.status),
721
- events: JSON.stringify(trace.events),
722
- links: JSON.stringify(trace.links),
723
- other: JSON.stringify(trace.other),
724
- };
725
- await table.add([record], { mode: 'append' });
310
+ async getMessagesPaginated(
311
+ args: StorageGetMessagesArg & { format?: 'v1' | 'v2' },
312
+ ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
313
+ return this.stores.memory.getMessagesPaginated(args);
314
+ }
726
315
 
727
- return trace;
728
- } catch (error: any) {
729
- throw new Error(`Failed to save trace: ${error}`);
730
- }
316
+ async updateMessages(_args: {
317
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
318
+ {
319
+ id: string;
320
+ content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
321
+ }[];
322
+ }): Promise<MastraMessageV2[]> {
323
+ return this.stores.memory.updateMessages(_args);
731
324
  }
732
325
 
733
- async getTraceById({ traceId }: { traceId: string }): Promise<TraceType> {
734
- try {
735
- const table = await this.lanceClient.openTable(TABLE_TRACES);
736
- const query = table.query().where(`id = '${traceId}'`);
737
- const records = await query.toArray();
738
- return this.processResultWithTypeConversion(records[0], await this.getTableSchema(TABLE_TRACES)) as TraceType;
739
- } catch (error: any) {
740
- throw new Error(`Failed to get trace by ID: ${error}`);
741
- }
326
+ async getTraceById(args: { traceId: string }): Promise<TraceType> {
327
+ return (this.stores as any).traces.getTraceById(args);
742
328
  }
743
329
 
744
- async getTraces({
745
- name,
746
- scope,
747
- page = 1,
748
- perPage = 10,
749
- attributes,
750
- }: {
330
+ async getTraces(args: {
751
331
  name?: string;
752
332
  scope?: string;
753
333
  page: number;
754
334
  perPage: number;
755
335
  attributes?: Record<string, string>;
756
- }): Promise<TraceType[]> {
757
- try {
758
- const table = await this.lanceClient.openTable(TABLE_TRACES);
759
- const query = table.query();
760
-
761
- if (name) {
762
- query.where(`name = '${name}'`);
763
- }
764
-
765
- if (scope) {
766
- query.where(`scope = '${scope}'`);
767
- }
768
-
769
- if (attributes) {
770
- query.where(`attributes = '${JSON.stringify(attributes)}'`);
771
- }
772
-
773
- // Calculate offset based on page and perPage
774
- const offset = (page - 1) * perPage;
775
-
776
- // Apply limit for pagination
777
- query.limit(perPage);
778
-
779
- // Apply offset if greater than 0
780
- if (offset > 0) {
781
- query.offset(offset);
782
- }
783
-
784
- const records = await query.toArray();
785
- return records.map(record => {
786
- return {
787
- ...record,
788
- attributes: JSON.parse(record.attributes),
789
- status: JSON.parse(record.status),
790
- events: JSON.parse(record.events),
791
- links: JSON.parse(record.links),
792
- other: JSON.parse(record.other),
793
- startTime: new Date(record.startTime),
794
- endTime: new Date(record.endTime),
795
- createdAt: new Date(record.createdAt),
796
- };
797
- }) as TraceType[];
798
- } catch (error: any) {
799
- throw new Error(`Failed to get traces: ${error}`);
800
- }
336
+ }): Promise<Trace[]> {
337
+ return (this.stores as any).traces.getTraces(args);
801
338
  }
802
339
 
803
- async saveEvals({ evals }: { evals: EvalRow[] }): Promise<EvalRow[]> {
804
- try {
805
- const table = await this.lanceClient.openTable(TABLE_EVALS);
806
- const transformedEvals = evals.map(evalRecord => ({
807
- input: evalRecord.input,
808
- output: evalRecord.output,
809
- agent_name: evalRecord.agentName,
810
- metric_name: evalRecord.metricName,
811
- result: JSON.stringify(evalRecord.result),
812
- instructions: evalRecord.instructions,
813
- test_info: JSON.stringify(evalRecord.testInfo),
814
- global_run_id: evalRecord.globalRunId,
815
- run_id: evalRecord.runId,
816
- created_at: new Date(evalRecord.createdAt).getTime(),
817
- }));
818
-
819
- await table.add(transformedEvals, { mode: 'append' });
820
- return evals;
821
- } catch (error: any) {
822
- throw new Error(`Failed to save evals: ${error}`);
823
- }
340
+ async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
341
+ return (this.stores as any).traces.getTracesPaginated(args);
824
342
  }
825
343
 
826
344
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
827
- try {
828
- if (type) {
829
- this.logger.warn('Type is not implemented yet in LanceDB storage');
830
- }
831
- const table = await this.lanceClient.openTable(TABLE_EVALS);
832
- const query = table.query().where(`agent_name = '${agentName}'`);
833
- const records = await query.toArray();
834
- return records.map(record => {
835
- return {
836
- id: record.id,
837
- input: record.input,
838
- output: record.output,
839
- agentName: record.agent_name,
840
- metricName: record.metric_name,
841
- result: JSON.parse(record.result),
842
- instructions: record.instructions,
843
- testInfo: JSON.parse(record.test_info),
844
- globalRunId: record.global_run_id,
845
- runId: record.run_id,
846
- createdAt: new Date(record.created_at).toString(),
847
- };
848
- }) as EvalRow[];
849
- } catch (error: any) {
850
- throw new Error(`Failed to get evals by agent name: ${error}`);
851
- }
345
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
852
346
  }
853
347
 
854
- private parseWorkflowRun(row: any): WorkflowRun {
855
- let parsedSnapshot: WorkflowRunState | string = row.snapshot as string;
856
- if (typeof parsedSnapshot === 'string') {
857
- try {
858
- parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
859
- } catch (e) {
860
- // If parsing fails, return the raw snapshot string
861
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
862
- }
863
- }
864
-
865
- return {
866
- workflowName: row.workflow_name,
867
- runId: row.run_id,
868
- snapshot: parsedSnapshot,
869
- createdAt: this.ensureDate(row.createdAt)!,
870
- updatedAt: this.ensureDate(row.updatedAt)!,
871
- resourceId: row.resourceId,
872
- };
348
+ async getEvals(options: {
349
+ agentName?: string;
350
+ type?: 'test' | 'live';
351
+ page?: number;
352
+ perPage?: number;
353
+ fromDate?: Date;
354
+ toDate?: Date;
355
+ dateRange?: { start?: Date; end?: Date };
356
+ }): Promise<PaginationInfo & { evals: EvalRow[] }> {
357
+ return this.stores.legacyEvals.getEvals(options);
873
358
  }
874
359
 
875
360
  async getWorkflowRuns(args?: {
@@ -880,45 +365,9 @@ export class LanceStorage extends MastraStorage {
880
365
  limit?: number;
881
366
  offset?: number;
882
367
  }): Promise<WorkflowRuns> {
883
- try {
884
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
885
- const query = table.query();
886
-
887
- if (args?.workflowName) {
888
- query.where(`workflow_name = '${args.workflowName}'`);
889
- }
890
-
891
- if (args?.fromDate) {
892
- query.where(`\`createdAt\` >= ${args.fromDate.getTime()}`);
893
- }
894
-
895
- if (args?.toDate) {
896
- query.where(`\`createdAt\` <= ${args.toDate.getTime()}`);
897
- }
898
-
899
- if (args?.limit) {
900
- query.limit(args.limit);
901
- }
902
-
903
- if (args?.offset) {
904
- query.offset(args.offset);
905
- }
906
-
907
- const records = await query.toArray();
908
- return {
909
- runs: records.map(record => this.parseWorkflowRun(record)),
910
- total: records.length,
911
- };
912
- } catch (error: any) {
913
- throw new Error(`Failed to get workflow runs: ${error}`);
914
- }
368
+ return this.stores.workflows.getWorkflowRuns(args);
915
369
  }
916
370
 
917
- /**
918
- * Retrieve a single workflow run by its runId.
919
- * @param args The ID of the workflow run to retrieve
920
- * @returns The workflow run object or null if not found
921
- */
922
371
  async getWorkflowRunById(args: { runId: string; workflowName?: string }): Promise<{
923
372
  workflowName: string;
924
373
  runId: string;
@@ -926,20 +375,7 @@ export class LanceStorage extends MastraStorage {
926
375
  createdAt: Date;
927
376
  updatedAt: Date;
928
377
  } | null> {
929
- try {
930
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
931
- let whereClause = `run_id = '${args.runId}'`;
932
- if (args.workflowName) {
933
- whereClause += ` AND workflow_name = '${args.workflowName}'`;
934
- }
935
- const query = table.query().where(whereClause);
936
- const records = await query.toArray();
937
- if (records.length === 0) return null;
938
- const record = records[0];
939
- return this.parseWorkflowRun(record);
940
- } catch (error: any) {
941
- throw new Error(`Failed to get workflow run by id: ${error}`);
942
- }
378
+ return this.stores.workflows.getWorkflowRunById(args);
943
379
  }
944
380
 
945
381
  async persistWorkflowSnapshot({
@@ -951,36 +387,9 @@ export class LanceStorage extends MastraStorage {
951
387
  runId: string;
952
388
  snapshot: WorkflowRunState;
953
389
  }): Promise<void> {
954
- try {
955
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
956
-
957
- // Try to find the existing record
958
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
959
- const records = await query.toArray();
960
- let createdAt: number;
961
- const now = Date.now();
962
- let mode: 'append' | 'overwrite' = 'append';
963
-
964
- if (records.length > 0) {
965
- createdAt = records[0].createdAt ?? now;
966
- mode = 'overwrite';
967
- } else {
968
- createdAt = now;
969
- }
970
-
971
- const record = {
972
- workflow_name: workflowName,
973
- run_id: runId,
974
- snapshot: JSON.stringify(snapshot),
975
- createdAt,
976
- updatedAt: now,
977
- };
978
-
979
- await table.add([record], { mode });
980
- } catch (error: any) {
981
- throw new Error(`Failed to persist workflow snapshot: ${error}`);
982
- }
390
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
983
391
  }
392
+
984
393
  async loadWorkflowSnapshot({
985
394
  workflowName,
986
395
  runId,
@@ -988,42 +397,46 @@ export class LanceStorage extends MastraStorage {
988
397
  workflowName: string;
989
398
  runId: string;
990
399
  }): Promise<WorkflowRunState | null> {
991
- try {
992
- const table = await this.lanceClient.openTable(TABLE_WORKFLOW_SNAPSHOT);
993
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
994
- const records = await query.toArray();
995
- return records.length > 0 ? JSON.parse(records[0].snapshot) : null;
996
- } catch (error: any) {
997
- throw new Error(`Failed to load workflow snapshot: ${error}`);
998
- }
400
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
999
401
  }
1000
402
 
1001
- async getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & { traces: Trace[] }> {
1002
- throw new Error('Method not implemented.');
403
+ async getScoreById({ id: _id }: { id: string }): Promise<ScoreRowData | null> {
404
+ return this.stores.scores.getScoreById({ id: _id });
1003
405
  }
1004
406
 
1005
- async getThreadsByResourceIdPaginated(_args: {
1006
- resourceId: string;
1007
- page?: number;
1008
- perPage?: number;
1009
- }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
1010
- throw new Error('Method not implemented.');
407
+ async getScoresByScorerId({
408
+ scorerId,
409
+ pagination,
410
+ }: {
411
+ scorerId: string;
412
+ pagination: StoragePagination;
413
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
414
+ return this.stores.scores.getScoresByScorerId({ scorerId, pagination });
1011
415
  }
1012
416
 
1013
- async getMessagesPaginated(
1014
- _args: StorageGetMessagesArg,
1015
- ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
1016
- throw new Error('Method not implemented.');
417
+ async saveScore(_score: ScoreRowData): Promise<{ score: ScoreRowData }> {
418
+ return this.stores.scores.saveScore(_score);
1017
419
  }
1018
420
 
1019
- async updateMessages(_args: {
1020
- messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
1021
- {
1022
- id: string;
1023
- content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
1024
- }[];
1025
- }): Promise<MastraMessageV2[]> {
1026
- this.logger.error('updateMessages is not yet implemented in LanceStore');
1027
- throw new Error('Method not implemented');
421
+ async getScoresByRunId({
422
+ runId,
423
+ pagination,
424
+ }: {
425
+ runId: string;
426
+ pagination: StoragePagination;
427
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
428
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
429
+ }
430
+
431
+ async getScoresByEntityId({
432
+ entityId,
433
+ entityType,
434
+ pagination,
435
+ }: {
436
+ pagination: StoragePagination;
437
+ entityId: string;
438
+ entityType: string;
439
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
440
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
1028
441
  }
1029
442
  }