@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.
@@ -0,0 +1,489 @@
1
+ import type { Connection } from '@lancedb/lancedb';
2
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
+ import { StoreOperations } from '@mastra/core/storage';
4
+ import type { TABLE_NAMES, StorageColumn } from '@mastra/core/storage';
5
+ import { Utf8, Int32, Float32, Binary, Schema, Field, Float64 } from 'apache-arrow';
6
+ import type { DataType } from 'apache-arrow';
7
+ import { getPrimaryKeys, getTableSchema, processResultWithTypeConversion, validateKeyTypes } from '../utils';
8
+
9
+ export class StoreOperationsLance extends StoreOperations {
10
+ client: Connection;
11
+ constructor({ client }: { client: Connection }) {
12
+ super();
13
+ this.client = client;
14
+ }
15
+
16
+ protected getDefaultValue(type: StorageColumn['type']): string {
17
+ switch (type) {
18
+ case 'text':
19
+ return "''";
20
+ case 'timestamp':
21
+ return 'CURRENT_TIMESTAMP';
22
+ case 'integer':
23
+ case 'bigint':
24
+ return '0';
25
+ case 'jsonb':
26
+ return "'{}'";
27
+ case 'uuid':
28
+ return "''";
29
+ default:
30
+ return super.getDefaultValue(type);
31
+ }
32
+ }
33
+
34
+ async hasColumn(tableName: TABLE_NAMES, columnName: string): Promise<boolean> {
35
+ const table = await this.client.openTable(tableName);
36
+ const schema = await table.schema();
37
+ return schema.fields.some(field => field.name === columnName);
38
+ }
39
+
40
+ private translateSchema(schema: Record<string, StorageColumn>): Schema {
41
+ const fields = Object.entries(schema).map(([name, column]) => {
42
+ // Convert string type to Arrow DataType
43
+ let arrowType: DataType;
44
+ switch (column.type.toLowerCase()) {
45
+ case 'text':
46
+ case 'uuid':
47
+ arrowType = new Utf8();
48
+ break;
49
+ case 'int':
50
+ case 'integer':
51
+ arrowType = new Int32();
52
+ break;
53
+ case 'bigint':
54
+ arrowType = new Float64();
55
+ break;
56
+ case 'float':
57
+ arrowType = new Float32();
58
+ break;
59
+ case 'jsonb':
60
+ case 'json':
61
+ arrowType = new Utf8();
62
+ break;
63
+ case 'binary':
64
+ arrowType = new Binary();
65
+ break;
66
+ case 'timestamp':
67
+ arrowType = new Float64();
68
+ break;
69
+ default:
70
+ // Default to string for unknown types
71
+ arrowType = new Utf8();
72
+ }
73
+
74
+ // Create a field with the appropriate arrow type
75
+ return new Field(name, arrowType, column.nullable ?? true);
76
+ });
77
+
78
+ return new Schema(fields);
79
+ }
80
+
81
+ async createTable({
82
+ tableName,
83
+ schema,
84
+ }: {
85
+ tableName: TABLE_NAMES;
86
+ schema: Record<string, StorageColumn>;
87
+ }): Promise<void> {
88
+ try {
89
+ if (!this.client) {
90
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
91
+ }
92
+ if (!tableName) {
93
+ throw new Error('tableName is required for createTable.');
94
+ }
95
+ if (!schema) {
96
+ throw new Error('schema is required for createTable.');
97
+ }
98
+ } catch (error) {
99
+ throw new MastraError(
100
+ {
101
+ id: 'STORAGE_LANCE_STORAGE_CREATE_TABLE_INVALID_ARGS',
102
+ domain: ErrorDomain.STORAGE,
103
+ category: ErrorCategory.USER,
104
+ details: { tableName },
105
+ },
106
+ error,
107
+ );
108
+ }
109
+
110
+ try {
111
+ const arrowSchema = this.translateSchema(schema);
112
+ await this.client.createEmptyTable(tableName, arrowSchema);
113
+ } catch (error: any) {
114
+ if (error.message?.includes('already exists')) {
115
+ this.logger.debug(`Table '${tableName}' already exists, skipping create`);
116
+ return;
117
+ }
118
+ throw new MastraError(
119
+ {
120
+ id: 'STORAGE_LANCE_STORAGE_CREATE_TABLE_FAILED',
121
+ domain: ErrorDomain.STORAGE,
122
+ category: ErrorCategory.THIRD_PARTY,
123
+ details: { tableName },
124
+ },
125
+ error,
126
+ );
127
+ }
128
+ }
129
+
130
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
131
+ try {
132
+ if (!this.client) {
133
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
134
+ }
135
+ if (!tableName) {
136
+ throw new Error('tableName is required for dropTable.');
137
+ }
138
+ } catch (validationError: any) {
139
+ throw new MastraError(
140
+ {
141
+ id: 'STORAGE_LANCE_STORAGE_DROP_TABLE_INVALID_ARGS',
142
+ domain: ErrorDomain.STORAGE,
143
+ category: ErrorCategory.USER,
144
+ text: validationError.message,
145
+ details: { tableName },
146
+ },
147
+ validationError,
148
+ );
149
+ }
150
+
151
+ try {
152
+ await this.client.dropTable(tableName);
153
+ } catch (error: any) {
154
+ if (error.toString().includes('was not found') || error.message?.includes('Table not found')) {
155
+ this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
156
+ return;
157
+ }
158
+ throw new MastraError(
159
+ {
160
+ id: 'STORAGE_LANCE_STORAGE_DROP_TABLE_FAILED',
161
+ domain: ErrorDomain.STORAGE,
162
+ category: ErrorCategory.THIRD_PARTY,
163
+ details: { tableName },
164
+ },
165
+ error,
166
+ );
167
+ }
168
+ }
169
+
170
+ async alterTable({
171
+ tableName,
172
+ schema,
173
+ ifNotExists,
174
+ }: {
175
+ tableName: string;
176
+ schema: Record<string, StorageColumn>;
177
+ ifNotExists: string[];
178
+ }): Promise<void> {
179
+ try {
180
+ if (!this.client) {
181
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
182
+ }
183
+ if (!tableName) {
184
+ throw new Error('tableName is required for alterTable.');
185
+ }
186
+ if (!schema) {
187
+ throw new Error('schema is required for alterTable.');
188
+ }
189
+ if (!ifNotExists || ifNotExists.length === 0) {
190
+ this.logger.debug('No columns specified to add in alterTable, skipping.');
191
+ return;
192
+ }
193
+ } catch (validationError: any) {
194
+ throw new MastraError(
195
+ {
196
+ id: 'STORAGE_LANCE_STORAGE_ALTER_TABLE_INVALID_ARGS',
197
+ domain: ErrorDomain.STORAGE,
198
+ category: ErrorCategory.USER,
199
+ text: validationError.message,
200
+ details: { tableName },
201
+ },
202
+ validationError,
203
+ );
204
+ }
205
+
206
+ try {
207
+ const table = await this.client.openTable(tableName);
208
+ const currentSchema = await table.schema();
209
+ const existingFields = new Set(currentSchema.fields.map((f: any) => f.name));
210
+
211
+ const typeMap: Record<string, string> = {
212
+ text: 'string',
213
+ integer: 'int',
214
+ bigint: 'bigint',
215
+ timestamp: 'timestamp',
216
+ jsonb: 'string',
217
+ uuid: 'string',
218
+ };
219
+
220
+ // Find columns to add
221
+ const columnsToAdd = ifNotExists
222
+ .filter(col => schema[col] && !existingFields.has(col))
223
+ .map(col => {
224
+ const colDef = schema[col];
225
+ return {
226
+ name: col,
227
+ valueSql: colDef?.nullable
228
+ ? `cast(NULL as ${typeMap[colDef.type ?? 'text']})`
229
+ : `cast(${this.getDefaultValue(colDef?.type ?? 'text')} as ${typeMap[colDef?.type ?? 'text']})`,
230
+ };
231
+ });
232
+
233
+ if (columnsToAdd.length > 0) {
234
+ await table.addColumns(columnsToAdd);
235
+ this.logger?.info?.(`Added columns [${columnsToAdd.map(c => c.name).join(', ')}] to table ${tableName}`);
236
+ }
237
+ } catch (error: any) {
238
+ throw new MastraError(
239
+ {
240
+ id: 'STORAGE_LANCE_STORAGE_ALTER_TABLE_FAILED',
241
+ domain: ErrorDomain.STORAGE,
242
+ category: ErrorCategory.THIRD_PARTY,
243
+ details: { tableName },
244
+ },
245
+ error,
246
+ );
247
+ }
248
+ }
249
+
250
+ async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
251
+ try {
252
+ if (!this.client) {
253
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
254
+ }
255
+ if (!tableName) {
256
+ throw new Error('tableName is required for clearTable.');
257
+ }
258
+ } catch (validationError: any) {
259
+ throw new MastraError(
260
+ {
261
+ id: 'STORAGE_LANCE_STORAGE_CLEAR_TABLE_INVALID_ARGS',
262
+ domain: ErrorDomain.STORAGE,
263
+ category: ErrorCategory.USER,
264
+ text: validationError.message,
265
+ details: { tableName },
266
+ },
267
+ validationError,
268
+ );
269
+ }
270
+
271
+ try {
272
+ const table = await this.client.openTable(tableName);
273
+
274
+ // delete function always takes a predicate as an argument, so we use '1=1' to delete all records because it is always true.
275
+ await table.delete('1=1');
276
+ } catch (error: any) {
277
+ throw new MastraError(
278
+ {
279
+ id: 'STORAGE_LANCE_STORAGE_CLEAR_TABLE_FAILED',
280
+ domain: ErrorDomain.STORAGE,
281
+ category: ErrorCategory.THIRD_PARTY,
282
+ details: { tableName },
283
+ },
284
+ error,
285
+ );
286
+ }
287
+ }
288
+
289
+ async insert({ tableName, record }: { tableName: string; record: Record<string, any> }): Promise<void> {
290
+ try {
291
+ if (!this.client) {
292
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
293
+ }
294
+ if (!tableName) {
295
+ throw new Error('tableName is required for insert.');
296
+ }
297
+ if (!record || Object.keys(record).length === 0) {
298
+ throw new Error('record is required and cannot be empty for insert.');
299
+ }
300
+ } catch (validationError: any) {
301
+ throw new MastraError(
302
+ {
303
+ id: 'STORAGE_LANCE_STORAGE_INSERT_INVALID_ARGS',
304
+ domain: ErrorDomain.STORAGE,
305
+ category: ErrorCategory.USER,
306
+ text: validationError.message,
307
+ details: { tableName },
308
+ },
309
+ validationError,
310
+ );
311
+ }
312
+
313
+ try {
314
+ const table = await this.client.openTable(tableName);
315
+
316
+ const primaryId = getPrimaryKeys(tableName as TABLE_NAMES);
317
+
318
+ const processedRecord = { ...record };
319
+
320
+ for (const key in processedRecord) {
321
+ if (
322
+ processedRecord[key] !== null &&
323
+ typeof processedRecord[key] === 'object' &&
324
+ !(processedRecord[key] instanceof Date)
325
+ ) {
326
+ this.logger.debug('Converting object to JSON string: ', processedRecord[key]);
327
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
328
+ }
329
+ }
330
+ console.log(await table.schema());
331
+
332
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
333
+ } catch (error: any) {
334
+ throw new MastraError(
335
+ {
336
+ id: 'STORAGE_LANCE_STORAGE_INSERT_FAILED',
337
+ domain: ErrorDomain.STORAGE,
338
+ category: ErrorCategory.THIRD_PARTY,
339
+ details: { tableName },
340
+ },
341
+ error,
342
+ );
343
+ }
344
+ }
345
+
346
+ async batchInsert({ tableName, records }: { tableName: string; records: Record<string, any>[] }): Promise<void> {
347
+ try {
348
+ if (!this.client) {
349
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
350
+ }
351
+ if (!tableName) {
352
+ throw new Error('tableName is required for batchInsert.');
353
+ }
354
+ if (!records || records.length === 0) {
355
+ throw new Error('records array is required and cannot be empty for batchInsert.');
356
+ }
357
+ } catch (validationError: any) {
358
+ throw new MastraError(
359
+ {
360
+ id: 'STORAGE_LANCE_STORAGE_BATCH_INSERT_INVALID_ARGS',
361
+ domain: ErrorDomain.STORAGE,
362
+ category: ErrorCategory.USER,
363
+ text: validationError.message,
364
+ details: { tableName },
365
+ },
366
+ validationError,
367
+ );
368
+ }
369
+
370
+ try {
371
+ const table = await this.client.openTable(tableName);
372
+
373
+ const primaryId = getPrimaryKeys(tableName as TABLE_NAMES);
374
+
375
+ const processedRecords = records.map(record => {
376
+ const processedRecord = { ...record };
377
+
378
+ // Convert values based on schema type
379
+ for (const key in processedRecord) {
380
+ // Skip null/undefined values
381
+ if (processedRecord[key] == null) continue;
382
+
383
+ if (
384
+ processedRecord[key] !== null &&
385
+ typeof processedRecord[key] === 'object' &&
386
+ !(processedRecord[key] instanceof Date)
387
+ ) {
388
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
389
+ }
390
+ }
391
+
392
+ return processedRecord;
393
+ });
394
+
395
+ console.log(processedRecords);
396
+
397
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
398
+ } catch (error: any) {
399
+ throw new MastraError(
400
+ {
401
+ id: 'STORAGE_LANCE_STORAGE_BATCH_INSERT_FAILED',
402
+ domain: ErrorDomain.STORAGE,
403
+ category: ErrorCategory.THIRD_PARTY,
404
+ details: { tableName },
405
+ },
406
+ error,
407
+ );
408
+ }
409
+ }
410
+
411
+ async load({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, any> }): Promise<any> {
412
+ try {
413
+ if (!this.client) {
414
+ throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
415
+ }
416
+ if (!tableName) {
417
+ throw new Error('tableName is required for load.');
418
+ }
419
+ if (!keys || Object.keys(keys).length === 0) {
420
+ throw new Error('keys are required and cannot be empty for load.');
421
+ }
422
+ } catch (validationError: any) {
423
+ throw new MastraError(
424
+ {
425
+ id: 'STORAGE_LANCE_STORAGE_LOAD_INVALID_ARGS',
426
+ domain: ErrorDomain.STORAGE,
427
+ category: ErrorCategory.USER,
428
+ text: validationError.message,
429
+ details: { tableName },
430
+ },
431
+ validationError,
432
+ );
433
+ }
434
+
435
+ try {
436
+ const table = await this.client.openTable(tableName);
437
+ const tableSchema = await getTableSchema({ tableName, client: this.client });
438
+ const query = table.query();
439
+
440
+ // Build filter condition with 'and' between all conditions
441
+ if (Object.keys(keys).length > 0) {
442
+ // Validate key types against schema
443
+ validateKeyTypes(keys, tableSchema);
444
+
445
+ const filterConditions = Object.entries(keys)
446
+ .map(([key, value]) => {
447
+ // Check if key is in camelCase and wrap it in backticks if it is
448
+ const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
449
+ const quotedKey = isCamelCase ? `\`${key}\`` : key;
450
+
451
+ // Handle different types appropriately
452
+ if (typeof value === 'string') {
453
+ return `${quotedKey} = '${value}'`;
454
+ } else if (value === null) {
455
+ return `${quotedKey} IS NULL`;
456
+ } else {
457
+ // For numbers, booleans, etc.
458
+ return `${quotedKey} = ${value}`;
459
+ }
460
+ })
461
+ .join(' AND ');
462
+
463
+ this.logger.debug('where clause generated: ' + filterConditions);
464
+ query.where(filterConditions);
465
+ }
466
+
467
+ const result = await query.limit(1).toArray();
468
+
469
+ if (result.length === 0) {
470
+ this.logger.debug('No record found');
471
+ return null;
472
+ }
473
+ // Process the result with type conversions
474
+ return processResultWithTypeConversion(result[0], tableSchema);
475
+ } catch (error: any) {
476
+ // If it's already a MastraError (e.g. from validateKeyTypes if we change it later), rethrow
477
+ if (error instanceof MastraError) throw error;
478
+ throw new MastraError(
479
+ {
480
+ id: 'STORAGE_LANCE_STORAGE_LOAD_FAILED',
481
+ domain: ErrorDomain.STORAGE,
482
+ category: ErrorCategory.THIRD_PARTY,
483
+ details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? '' },
484
+ },
485
+ error,
486
+ );
487
+ }
488
+ }
489
+ }
@@ -0,0 +1,221 @@
1
+ import type { Connection } from '@lancedb/lancedb';
2
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
+ import type { ScoreRowData } from '@mastra/core/scores';
4
+ import { ScoresStorage, TABLE_SCORERS } from '@mastra/core/storage';
5
+ import type { PaginationInfo, StoragePagination } from '@mastra/core/storage';
6
+ import { getTableSchema, processResultWithTypeConversion } from '../utils';
7
+
8
+ export class StoreScoresLance extends ScoresStorage {
9
+ private client: Connection;
10
+ constructor({ client }: { client: Connection }) {
11
+ super();
12
+ this.client = client;
13
+ }
14
+
15
+ async saveScore(score: ScoreRowData): Promise<{ score: ScoreRowData }> {
16
+ try {
17
+ const table = await this.client.openTable(TABLE_SCORERS);
18
+ // Fetch schema fields for mastra_scorers
19
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
20
+ const allowedFields = new Set(schema.fields.map((f: any) => f.name));
21
+ // Filter out fields not in schema
22
+ const filteredScore: Record<string, any> = {};
23
+ (Object.keys(score) as (keyof ScoreRowData)[]).forEach(key => {
24
+ if (allowedFields.has(key)) {
25
+ filteredScore[key] = score[key];
26
+ }
27
+ });
28
+ // Convert any object fields to JSON strings for storage
29
+ for (const key in filteredScore) {
30
+ if (
31
+ filteredScore[key] !== null &&
32
+ typeof filteredScore[key] === 'object' &&
33
+ !(filteredScore[key] instanceof Date)
34
+ ) {
35
+ filteredScore[key] = JSON.stringify(filteredScore[key]);
36
+ }
37
+ }
38
+
39
+ console.log('Saving score to LanceStorage:', filteredScore);
40
+
41
+ await table.add([filteredScore], { mode: 'append' });
42
+ return { score };
43
+ } catch (error: any) {
44
+ throw new MastraError(
45
+ {
46
+ id: 'LANCE_STORAGE_SAVE_SCORE_FAILED',
47
+ text: 'Failed to save score in LanceStorage',
48
+ domain: ErrorDomain.STORAGE,
49
+ category: ErrorCategory.THIRD_PARTY,
50
+ details: { error: error?.message },
51
+ },
52
+ error,
53
+ );
54
+ }
55
+ }
56
+
57
+ async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
58
+ try {
59
+ const table = await this.client.openTable(TABLE_SCORERS);
60
+
61
+ const query = table.query().where(`id = '${id}'`).limit(1);
62
+
63
+ const records = await query.toArray();
64
+
65
+ if (records.length === 0) return null;
66
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
67
+ return processResultWithTypeConversion(records[0], schema) as ScoreRowData;
68
+ } catch (error: any) {
69
+ throw new MastraError(
70
+ {
71
+ id: 'LANCE_STORAGE_GET_SCORE_BY_ID_FAILED',
72
+ text: 'Failed to get score by id in LanceStorage',
73
+ domain: ErrorDomain.STORAGE,
74
+ category: ErrorCategory.THIRD_PARTY,
75
+ details: { error: error?.message },
76
+ },
77
+ error,
78
+ );
79
+ }
80
+ }
81
+
82
+ async getScoresByScorerId({
83
+ scorerId,
84
+ pagination,
85
+ }: {
86
+ scorerId: string;
87
+ pagination: StoragePagination;
88
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
89
+ try {
90
+ const table = await this.client.openTable(TABLE_SCORERS);
91
+ // Use zero-based pagination (default page = 0)
92
+ const { page = 0, perPage = 10 } = pagination || {};
93
+ const offset = page * perPage;
94
+ // Query for scores with the given scorerId
95
+ // Must use backticks for field names to handle camelCase
96
+ const query = table.query().where(`\`scorerId\` = '${scorerId}'`).limit(perPage);
97
+ if (offset > 0) query.offset(offset);
98
+ const records = await query.toArray();
99
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
100
+ const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
101
+
102
+ const allRecords = await table.query().where(`\`scorerId\` = '${scorerId}'`).toArray();
103
+ const total = allRecords.length;
104
+
105
+ return {
106
+ pagination: {
107
+ page,
108
+ perPage,
109
+ total,
110
+ hasMore: offset + scores.length < total,
111
+ },
112
+ scores,
113
+ };
114
+ } catch (error: any) {
115
+ throw new MastraError(
116
+ {
117
+ id: 'LANCE_STORAGE_GET_SCORES_BY_SCORER_ID_FAILED',
118
+ text: 'Failed to get scores by scorerId in LanceStorage',
119
+ domain: ErrorDomain.STORAGE,
120
+ category: ErrorCategory.THIRD_PARTY,
121
+ details: { error: error?.message },
122
+ },
123
+ error,
124
+ );
125
+ }
126
+ }
127
+
128
+ async getScoresByRunId({
129
+ runId,
130
+ pagination,
131
+ }: {
132
+ runId: string;
133
+ pagination: StoragePagination;
134
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
135
+ try {
136
+ const table = await this.client.openTable(TABLE_SCORERS);
137
+ const { page = 0, perPage = 10 } = pagination || {};
138
+ const offset = page * perPage;
139
+ // Query for scores with the given runId
140
+ const query = table.query().where(`\`runId\` = '${runId}'`).limit(perPage);
141
+ if (offset > 0) query.offset(offset);
142
+ const records = await query.toArray();
143
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
144
+ const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
145
+ // Get total count for pagination
146
+ const allRecords = await table.query().where(`\`runId\` = '${runId}'`).toArray();
147
+ const total = allRecords.length;
148
+ return {
149
+ pagination: {
150
+ page,
151
+ perPage,
152
+ total,
153
+ hasMore: offset + scores.length < total,
154
+ },
155
+ scores,
156
+ };
157
+ } catch (error: any) {
158
+ throw new MastraError(
159
+ {
160
+ id: 'LANCE_STORAGE_GET_SCORES_BY_RUN_ID_FAILED',
161
+ text: 'Failed to get scores by runId in LanceStorage',
162
+ domain: ErrorDomain.STORAGE,
163
+ category: ErrorCategory.THIRD_PARTY,
164
+ details: { error: error?.message },
165
+ },
166
+ error,
167
+ );
168
+ }
169
+ }
170
+
171
+ async getScoresByEntityId({
172
+ entityId,
173
+ entityType,
174
+ pagination,
175
+ }: {
176
+ pagination: StoragePagination;
177
+ entityId: string;
178
+ entityType: string;
179
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
180
+ try {
181
+ const table = await this.client.openTable(TABLE_SCORERS);
182
+ const { page = 0, perPage = 10 } = pagination || {};
183
+ const offset = page * perPage;
184
+ // Query for scores with the given entityId and entityType
185
+ const query = table
186
+ .query()
187
+ .where(`\`entityId\` = '${entityId}' AND \`entityType\` = '${entityType}'`)
188
+ .limit(perPage);
189
+ if (offset > 0) query.offset(offset);
190
+ const records = await query.toArray();
191
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
192
+ const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
193
+ // Get total count for pagination
194
+ const allRecords = await table
195
+ .query()
196
+ .where(`\`entityId\` = '${entityId}' AND \`entityType\` = '${entityType}'`)
197
+ .toArray();
198
+ const total = allRecords.length;
199
+ return {
200
+ pagination: {
201
+ page,
202
+ perPage,
203
+ total,
204
+ hasMore: offset + scores.length < total,
205
+ },
206
+ scores,
207
+ };
208
+ } catch (error: any) {
209
+ throw new MastraError(
210
+ {
211
+ id: 'LANCE_STORAGE_GET_SCORES_BY_ENTITY_ID_FAILED',
212
+ text: 'Failed to get scores by entityId and entityType in LanceStorage',
213
+ domain: ErrorDomain.STORAGE,
214
+ category: ErrorCategory.THIRD_PARTY,
215
+ details: { error: error?.message },
216
+ },
217
+ error,
218
+ );
219
+ }
220
+ }
221
+ }