@mastra/lance 1.0.0-beta.1 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { connect, Index } from '@lancedb/lancedb';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { MastraStorage, StoreOperations, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, normalizePerPage, calculatePagination, TABLE_RESOURCES, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, ensureDate } from '@mastra/core/storage';
3
+ import { MemoryStorage, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, createStorageErrorId, normalizePerPage, calculatePagination, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, createVectorErrorId, getDefaultValue, ensureDate } from '@mastra/core/storage';
4
4
  import { MessageList } from '@mastra/core/agent';
5
+ import { MastraBase } from '@mastra/core/base';
5
6
  import { Utf8, Float64, Binary, Float32, Int32, Field, Schema } from 'apache-arrow';
6
7
  import { saveScorePayloadSchema } from '@mastra/core/evals';
7
8
  import { MastraVector } from '@mastra/core/vector';
@@ -89,7 +90,7 @@ async function getTableSchema({
89
90
  } catch (validationError) {
90
91
  throw new MastraError(
91
92
  {
92
- id: "STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_INVALID_ARGS",
93
+ id: createStorageErrorId("LANCE", "GET_TABLE_SCHEMA", "INVALID_ARGS"),
93
94
  domain: ErrorDomain.STORAGE,
94
95
  category: ErrorCategory.USER,
95
96
  text: validationError.message,
@@ -112,7 +113,7 @@ async function getTableSchema({
112
113
  } catch (error) {
113
114
  throw new MastraError(
114
115
  {
115
- id: "STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_FAILED",
116
+ id: createStorageErrorId("LANCE", "GET_TABLE_SCHEMA", "FAILED"),
116
117
  domain: ErrorDomain.STORAGE,
117
118
  category: ErrorCategory.THIRD_PARTY,
118
119
  details: { tableName }
@@ -122,1118 +123,1200 @@ async function getTableSchema({
122
123
  }
123
124
  }
124
125
 
125
- // src/storage/domains/memory/index.ts
126
- var StoreMemoryLance = class extends MemoryStorage {
126
+ // src/storage/db/index.ts
127
+ function resolveLanceConfig(config) {
128
+ return config.client;
129
+ }
130
+ var LanceDB = class extends MastraBase {
127
131
  client;
128
- operations;
129
- constructor({ client, operations }) {
130
- super();
132
+ constructor({ client }) {
133
+ super({ name: "lance-db" });
131
134
  this.client = client;
132
- this.operations = operations;
133
135
  }
134
- // Utility to escape single quotes in SQL strings
135
- escapeSql(str) {
136
- return str.replace(/'/g, "''");
136
+ getDefaultValue(type) {
137
+ switch (type) {
138
+ case "text":
139
+ return "''";
140
+ case "timestamp":
141
+ return "CURRENT_TIMESTAMP";
142
+ case "integer":
143
+ case "bigint":
144
+ return "0";
145
+ case "jsonb":
146
+ return "'{}'";
147
+ case "uuid":
148
+ return "''";
149
+ default:
150
+ return getDefaultValue(type);
151
+ }
137
152
  }
138
- async getThreadById({ threadId }) {
153
+ async hasColumn(tableName, columnName) {
154
+ const table = await this.client.openTable(tableName);
155
+ const schema = await table.schema();
156
+ return schema.fields.some((field) => field.name === columnName);
157
+ }
158
+ translateSchema(schema) {
159
+ const fields = Object.entries(schema).map(([name, column]) => {
160
+ let arrowType;
161
+ switch (column.type.toLowerCase()) {
162
+ case "text":
163
+ case "uuid":
164
+ arrowType = new Utf8();
165
+ break;
166
+ case "int":
167
+ case "integer":
168
+ arrowType = new Int32();
169
+ break;
170
+ case "bigint":
171
+ arrowType = new Float64();
172
+ break;
173
+ case "float":
174
+ arrowType = new Float32();
175
+ break;
176
+ case "jsonb":
177
+ case "json":
178
+ arrowType = new Utf8();
179
+ break;
180
+ case "binary":
181
+ arrowType = new Binary();
182
+ break;
183
+ case "timestamp":
184
+ arrowType = new Float64();
185
+ break;
186
+ default:
187
+ arrowType = new Utf8();
188
+ }
189
+ return new Field(name, arrowType, column.nullable ?? true);
190
+ });
191
+ return new Schema(fields);
192
+ }
193
+ async createTable({
194
+ tableName,
195
+ schema
196
+ }) {
139
197
  try {
140
- const thread = await this.operations.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
141
- if (!thread) {
142
- return null;
198
+ if (!this.client) {
199
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
200
+ }
201
+ if (!tableName) {
202
+ throw new Error("tableName is required for createTable.");
203
+ }
204
+ if (!schema) {
205
+ throw new Error("schema is required for createTable.");
143
206
  }
144
- return {
145
- ...thread,
146
- createdAt: new Date(thread.createdAt),
147
- updatedAt: new Date(thread.updatedAt)
148
- };
149
207
  } catch (error) {
150
208
  throw new MastraError(
151
209
  {
152
- id: "LANCE_STORE_GET_THREAD_BY_ID_FAILED",
210
+ id: createStorageErrorId("LANCE", "CREATE_TABLE", "INVALID_ARGS"),
153
211
  domain: ErrorDomain.STORAGE,
154
- category: ErrorCategory.THIRD_PARTY
212
+ category: ErrorCategory.USER,
213
+ details: { tableName }
155
214
  },
156
215
  error
157
216
  );
158
217
  }
159
- }
160
- /**
161
- * Saves a thread to the database. This function doesn't overwrite existing threads.
162
- * @param thread - The thread to save
163
- * @returns The saved thread
164
- */
165
- async saveThread({ thread }) {
166
218
  try {
167
- const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
168
- const table = await this.client.openTable(TABLE_THREADS);
169
- await table.add([record], { mode: "append" });
170
- return thread;
219
+ const arrowSchema = this.translateSchema(schema);
220
+ await this.client.createEmptyTable(tableName, arrowSchema);
171
221
  } catch (error) {
222
+ if (error.message?.includes("already exists")) {
223
+ this.logger.debug(`Table '${tableName}' already exists, skipping create`);
224
+ return;
225
+ }
172
226
  throw new MastraError(
173
227
  {
174
- id: "LANCE_STORE_SAVE_THREAD_FAILED",
228
+ id: createStorageErrorId("LANCE", "CREATE_TABLE", "FAILED"),
175
229
  domain: ErrorDomain.STORAGE,
176
- category: ErrorCategory.THIRD_PARTY
230
+ category: ErrorCategory.THIRD_PARTY,
231
+ details: { tableName }
177
232
  },
178
233
  error
179
234
  );
180
235
  }
181
236
  }
182
- async updateThread({
183
- id,
184
- title,
185
- metadata
186
- }) {
187
- const maxRetries = 5;
188
- for (let attempt = 0; attempt < maxRetries; attempt++) {
189
- try {
190
- const current = await this.getThreadById({ threadId: id });
191
- if (!current) {
192
- throw new Error(`Thread with id ${id} not found`);
193
- }
194
- const mergedMetadata = { ...current.metadata, ...metadata };
195
- const record = {
196
- id,
197
- title,
198
- metadata: JSON.stringify(mergedMetadata),
199
- updatedAt: (/* @__PURE__ */ new Date()).getTime()
200
- };
201
- const table = await this.client.openTable(TABLE_THREADS);
202
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
203
- const updatedThread = await this.getThreadById({ threadId: id });
204
- if (!updatedThread) {
205
- throw new Error(`Failed to retrieve updated thread ${id}`);
206
- }
207
- return updatedThread;
208
- } catch (error) {
209
- if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
210
- const delay = Math.pow(2, attempt) * 10;
211
- await new Promise((resolve) => setTimeout(resolve, delay));
212
- continue;
213
- }
214
- throw new MastraError(
215
- {
216
- id: "LANCE_STORE_UPDATE_THREAD_FAILED",
217
- domain: ErrorDomain.STORAGE,
218
- category: ErrorCategory.THIRD_PARTY
219
- },
220
- error
221
- );
222
- }
223
- }
224
- throw new MastraError(
225
- {
226
- id: "LANCE_STORE_UPDATE_THREAD_FAILED",
227
- domain: ErrorDomain.STORAGE,
228
- category: ErrorCategory.THIRD_PARTY
229
- },
230
- new Error("All retries exhausted")
231
- );
232
- }
233
- async deleteThread({ threadId }) {
237
+ async dropTable({ tableName }) {
234
238
  try {
235
- const table = await this.client.openTable(TABLE_THREADS);
236
- await table.delete(`id = '${threadId}'`);
237
- const messagesTable = await this.client.openTable(TABLE_MESSAGES);
238
- await messagesTable.delete(`thread_id = '${threadId}'`);
239
- } catch (error) {
239
+ if (!this.client) {
240
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
241
+ }
242
+ if (!tableName) {
243
+ throw new Error("tableName is required for dropTable.");
244
+ }
245
+ } catch (validationError) {
240
246
  throw new MastraError(
241
247
  {
242
- id: "LANCE_STORE_DELETE_THREAD_FAILED",
248
+ id: createStorageErrorId("LANCE", "DROP_TABLE", "INVALID_ARGS"),
243
249
  domain: ErrorDomain.STORAGE,
244
- category: ErrorCategory.THIRD_PARTY
250
+ category: ErrorCategory.USER,
251
+ text: validationError.message,
252
+ details: { tableName }
245
253
  },
246
- error
254
+ validationError
247
255
  );
248
256
  }
249
- }
250
- normalizeMessage(message) {
251
- const { thread_id, ...rest } = message;
252
- return {
253
- ...rest,
254
- threadId: thread_id,
255
- content: typeof message.content === "string" ? (() => {
256
- try {
257
- return JSON.parse(message.content);
258
- } catch {
259
- return message.content;
260
- }
261
- })() : message.content
262
- };
263
- }
264
- async listMessagesById({ messageIds }) {
265
- if (messageIds.length === 0) return { messages: [] };
266
257
  try {
267
- const table = await this.client.openTable(TABLE_MESSAGES);
268
- const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
269
- const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
270
- const messages = processResultWithTypeConversion(
271
- allRecords,
272
- await getTableSchema({ tableName: TABLE_MESSAGES, client: this.client })
273
- );
274
- const list = new MessageList().add(
275
- messages.map(this.normalizeMessage),
276
- "memory"
277
- );
278
- return { messages: list.get.all.db() };
258
+ await this.client.dropTable(tableName);
279
259
  } catch (error) {
260
+ if (error.toString().includes("was not found") || error.message?.includes("Table not found")) {
261
+ this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
262
+ return;
263
+ }
280
264
  throw new MastraError(
281
265
  {
282
- id: "LANCE_STORE_LIST_MESSAGES_BY_ID_FAILED",
266
+ id: createStorageErrorId("LANCE", "DROP_TABLE", "FAILED"),
283
267
  domain: ErrorDomain.STORAGE,
284
268
  category: ErrorCategory.THIRD_PARTY,
285
- details: {
286
- messageIds: JSON.stringify(messageIds)
287
- }
269
+ details: { tableName }
288
270
  },
289
271
  error
290
272
  );
291
273
  }
292
274
  }
293
- async listMessages(args) {
294
- const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
295
- if (!threadId.trim()) {
275
+ async alterTable({
276
+ tableName,
277
+ schema,
278
+ ifNotExists
279
+ }) {
280
+ try {
281
+ if (!this.client) {
282
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
283
+ }
284
+ if (!tableName) {
285
+ throw new Error("tableName is required for alterTable.");
286
+ }
287
+ if (!schema) {
288
+ throw new Error("schema is required for alterTable.");
289
+ }
290
+ if (!ifNotExists || ifNotExists.length === 0) {
291
+ this.logger.debug("No columns specified to add in alterTable, skipping.");
292
+ return;
293
+ }
294
+ } catch (validationError) {
296
295
  throw new MastraError(
297
296
  {
298
- id: "STORAGE_LANCE_LIST_MESSAGES_INVALID_THREAD_ID",
297
+ id: createStorageErrorId("LANCE", "ALTER_TABLE", "INVALID_ARGS"),
299
298
  domain: ErrorDomain.STORAGE,
300
- category: ErrorCategory.THIRD_PARTY,
301
- details: { threadId }
299
+ category: ErrorCategory.USER,
300
+ text: validationError.message,
301
+ details: { tableName }
302
302
  },
303
- new Error("threadId must be a non-empty string")
303
+ validationError
304
304
  );
305
305
  }
306
- const perPage = normalizePerPage(perPageInput, 40);
307
- const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
308
306
  try {
309
- if (page < 0) {
310
- throw new MastraError(
311
- {
312
- id: "STORAGE_LANCE_LIST_MESSAGES_INVALID_PAGE",
313
- domain: ErrorDomain.STORAGE,
314
- category: ErrorCategory.USER,
315
- details: { page }
316
- },
317
- new Error("page must be >= 0")
318
- );
319
- }
320
- const { field, direction } = this.parseOrderBy(orderBy, "ASC");
321
- const table = await this.client.openTable(TABLE_MESSAGES);
322
- const conditions = [`thread_id = '${this.escapeSql(threadId)}'`];
323
- if (resourceId) {
324
- conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
325
- }
326
- if (filter?.dateRange?.start) {
327
- const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
328
- conditions.push(`\`createdAt\` >= ${startTime}`);
329
- }
330
- if (filter?.dateRange?.end) {
331
- const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
332
- conditions.push(`\`createdAt\` <= ${endTime}`);
333
- }
334
- const whereClause = conditions.join(" AND ");
335
- const total = await table.countRows(whereClause);
336
- const query = table.query().where(whereClause);
337
- let allRecords = await query.toArray();
338
- allRecords.sort((a, b) => {
339
- const aValue = field === "createdAt" ? a.createdAt : a[field];
340
- const bValue = field === "createdAt" ? b.createdAt : b[field];
341
- if (aValue == null && bValue == null) return 0;
342
- if (aValue == null) return direction === "ASC" ? -1 : 1;
343
- if (bValue == null) return direction === "ASC" ? 1 : -1;
344
- if (typeof aValue === "string" && typeof bValue === "string") {
345
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
346
- }
347
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
348
- });
349
- const paginatedRecords = allRecords.slice(offset, offset + perPage);
350
- const messages = paginatedRecords.map((row) => this.normalizeMessage(row));
351
- if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
307
+ const table = await this.client.openTable(tableName);
308
+ const currentSchema = await table.schema();
309
+ const existingFields = new Set(currentSchema.fields.map((f) => f.name));
310
+ const typeMap = {
311
+ text: "string",
312
+ integer: "int",
313
+ bigint: "bigint",
314
+ timestamp: "timestamp",
315
+ jsonb: "string",
316
+ uuid: "string"
317
+ };
318
+ const columnsToAdd = ifNotExists.filter((col) => schema[col] && !existingFields.has(col)).map((col) => {
319
+ const colDef = schema[col];
352
320
  return {
353
- messages: [],
354
- total: 0,
355
- page,
356
- perPage: perPageForResponse,
357
- hasMore: false
321
+ name: col,
322
+ valueSql: colDef?.nullable ? `cast(NULL as ${typeMap[colDef.type ?? "text"]})` : `cast(${this.getDefaultValue(colDef?.type ?? "text")} as ${typeMap[colDef?.type ?? "text"]})`
358
323
  };
359
- }
360
- const messageIds = new Set(messages.map((m) => m.id));
361
- if (include && include.length > 0) {
362
- const threadIds = [...new Set(include.map((item) => item.threadId || threadId))];
363
- const allThreadMessages = [];
364
- for (const tid of threadIds) {
365
- const threadQuery = table.query().where(`thread_id = '${tid}'`);
366
- let threadRecords = await threadQuery.toArray();
367
- allThreadMessages.push(...threadRecords);
368
- }
369
- allThreadMessages.sort((a, b) => a.createdAt - b.createdAt);
370
- const contextMessages = this.processMessagesWithContext(allThreadMessages, include);
371
- const includedMessages = contextMessages.map((row) => this.normalizeMessage(row));
372
- for (const includeMsg of includedMessages) {
373
- if (!messageIds.has(includeMsg.id)) {
374
- messages.push(includeMsg);
375
- messageIds.add(includeMsg.id);
376
- }
377
- }
378
- }
379
- const list = new MessageList().add(messages, "memory");
380
- let finalMessages = list.get.all.db();
381
- finalMessages = finalMessages.sort((a, b) => {
382
- const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
383
- const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
384
- if (aValue == null && bValue == null) return 0;
385
- if (aValue == null) return direction === "ASC" ? -1 : 1;
386
- if (bValue == null) return direction === "ASC" ? 1 : -1;
387
- if (typeof aValue === "string" && typeof bValue === "string") {
388
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
389
- }
390
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
391
324
  });
392
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
393
- const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
394
- const fetchedAll = perPageInput === false || allThreadMessagesReturned;
395
- const hasMore = !fetchedAll && offset + perPage < total;
396
- return {
397
- messages: finalMessages,
398
- total,
399
- page,
400
- perPage: perPageForResponse,
401
- hasMore
402
- };
325
+ if (columnsToAdd.length > 0) {
326
+ await table.addColumns(columnsToAdd);
327
+ this.logger?.info?.(`Added columns [${columnsToAdd.map((c) => c.name).join(", ")}] to table ${tableName}`);
328
+ }
403
329
  } catch (error) {
404
- const mastraError = new MastraError(
330
+ throw new MastraError(
405
331
  {
406
- id: "LANCE_STORE_LIST_MESSAGES_FAILED",
332
+ id: createStorageErrorId("LANCE", "ALTER_TABLE", "FAILED"),
407
333
  domain: ErrorDomain.STORAGE,
408
334
  category: ErrorCategory.THIRD_PARTY,
409
- details: {
410
- threadId,
411
- resourceId: resourceId ?? ""
412
- }
335
+ details: { tableName }
413
336
  },
414
337
  error
415
338
  );
416
- this.logger?.error?.(mastraError.toString());
417
- this.logger?.trackException?.(mastraError);
418
- return {
419
- messages: [],
420
- total: 0,
421
- page,
422
- perPage: perPageForResponse,
423
- hasMore: false
424
- };
425
339
  }
426
340
  }
427
- async saveMessages(args) {
341
+ async clearTable({ tableName }) {
428
342
  try {
429
- const { messages } = args;
430
- if (messages.length === 0) {
431
- return { messages: [] };
432
- }
433
- const threadId = messages[0]?.threadId;
434
- if (!threadId) {
435
- throw new Error("Thread ID is required");
343
+ if (!this.client) {
344
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
436
345
  }
437
- for (const message of messages) {
438
- if (!message.id) {
439
- throw new Error("Message ID is required");
440
- }
441
- if (!message.threadId) {
442
- throw new Error("Thread ID is required for all messages");
443
- }
444
- if (message.resourceId === null || message.resourceId === void 0) {
445
- throw new Error("Resource ID cannot be null or undefined");
446
- }
447
- if (!message.content) {
448
- throw new Error("Message content is required");
449
- }
346
+ if (!tableName) {
347
+ throw new Error("tableName is required for clearTable.");
450
348
  }
451
- const transformedMessages = messages.map((message) => {
452
- const { threadId: threadId2, type, ...rest } = message;
453
- return {
454
- ...rest,
455
- thread_id: threadId2,
456
- type: type ?? "v2",
457
- content: JSON.stringify(message.content)
458
- };
459
- });
460
- const table = await this.client.openTable(TABLE_MESSAGES);
461
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
462
- const threadsTable = await this.client.openTable(TABLE_THREADS);
463
- const currentTime = (/* @__PURE__ */ new Date()).getTime();
464
- const updateRecord = { id: threadId, updatedAt: currentTime };
465
- await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([updateRecord]);
466
- const list = new MessageList().add(messages, "memory");
467
- return { messages: list.get.all.db() };
349
+ } catch (validationError) {
350
+ throw new MastraError(
351
+ {
352
+ id: createStorageErrorId("LANCE", "CLEAR_TABLE", "INVALID_ARGS"),
353
+ domain: ErrorDomain.STORAGE,
354
+ category: ErrorCategory.USER,
355
+ text: validationError.message,
356
+ details: { tableName }
357
+ },
358
+ validationError
359
+ );
360
+ }
361
+ try {
362
+ const table = await this.client.openTable(tableName);
363
+ await table.delete("1=1");
468
364
  } catch (error) {
469
365
  throw new MastraError(
470
366
  {
471
- id: "LANCE_STORE_SAVE_MESSAGES_FAILED",
367
+ id: createStorageErrorId("LANCE", "CLEAR_TABLE", "FAILED"),
472
368
  domain: ErrorDomain.STORAGE,
473
- category: ErrorCategory.THIRD_PARTY
369
+ category: ErrorCategory.THIRD_PARTY,
370
+ details: { tableName }
474
371
  },
475
372
  error
476
373
  );
477
374
  }
478
375
  }
479
- async listThreadsByResourceId(args) {
376
+ async insert({ tableName, record }) {
480
377
  try {
481
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
482
- const perPage = normalizePerPage(perPageInput, 100);
483
- if (page < 0) {
484
- throw new MastraError(
485
- {
486
- id: "STORAGE_LANCE_LIST_THREADS_BY_RESOURCE_ID_INVALID_PAGE",
487
- domain: ErrorDomain.STORAGE,
488
- category: ErrorCategory.USER,
489
- details: { page }
490
- },
491
- new Error("page must be >= 0")
492
- );
378
+ if (!this.client) {
379
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
493
380
  }
494
- const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
495
- const { field, direction } = this.parseOrderBy(orderBy);
496
- const table = await this.client.openTable(TABLE_THREADS);
497
- const total = await table.countRows(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
498
- const query = table.query().where(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
499
- const records = await query.toArray();
500
- records.sort((a, b) => {
501
- const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
502
- const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
503
- if (aValue == null && bValue == null) return 0;
504
- if (aValue == null) return direction === "ASC" ? -1 : 1;
505
- if (bValue == null) return direction === "ASC" ? 1 : -1;
506
- if (typeof aValue === "string" && typeof bValue === "string") {
507
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
508
- }
509
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
510
- });
511
- const paginatedRecords = records.slice(offset, offset + perPage);
512
- const schema = await getTableSchema({ tableName: TABLE_THREADS, client: this.client });
513
- const threads = paginatedRecords.map(
514
- (record) => processResultWithTypeConversion(record, schema)
381
+ if (!tableName) {
382
+ throw new Error("tableName is required for insert.");
383
+ }
384
+ if (!record || Object.keys(record).length === 0) {
385
+ throw new Error("record is required and cannot be empty for insert.");
386
+ }
387
+ } catch (validationError) {
388
+ throw new MastraError(
389
+ {
390
+ id: createStorageErrorId("LANCE", "INSERT", "INVALID_ARGS"),
391
+ domain: ErrorDomain.STORAGE,
392
+ category: ErrorCategory.USER,
393
+ text: validationError.message,
394
+ details: { tableName }
395
+ },
396
+ validationError
515
397
  );
516
- return {
517
- threads,
518
- total,
519
- page,
520
- perPage: perPageForResponse,
521
- hasMore: offset + perPage < total
522
- };
398
+ }
399
+ try {
400
+ const table = await this.client.openTable(tableName);
401
+ const primaryId = getPrimaryKeys(tableName);
402
+ const processedRecord = { ...record };
403
+ for (const key in processedRecord) {
404
+ if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
405
+ this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
406
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
407
+ }
408
+ }
409
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
523
410
  } catch (error) {
524
411
  throw new MastraError(
525
412
  {
526
- id: "LANCE_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
413
+ id: createStorageErrorId("LANCE", "INSERT", "FAILED"),
527
414
  domain: ErrorDomain.STORAGE,
528
- category: ErrorCategory.THIRD_PARTY
415
+ category: ErrorCategory.THIRD_PARTY,
416
+ details: { tableName }
529
417
  },
530
418
  error
531
419
  );
532
420
  }
533
421
  }
534
- /**
535
- * Processes messages to include context messages based on withPreviousMessages and withNextMessages
536
- * @param records - The sorted array of records to process
537
- * @param include - The array of include specifications with context parameters
538
- * @returns The processed array with context messages included
539
- */
540
- processMessagesWithContext(records, include) {
541
- const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
542
- if (messagesWithContext.length === 0) {
543
- return records;
544
- }
545
- const messageIndexMap = /* @__PURE__ */ new Map();
546
- records.forEach((message, index) => {
547
- messageIndexMap.set(message.id, index);
548
- });
549
- const additionalIndices = /* @__PURE__ */ new Set();
550
- for (const item of messagesWithContext) {
551
- const messageIndex = messageIndexMap.get(item.id);
552
- if (messageIndex !== void 0) {
553
- if (item.withPreviousMessages) {
554
- const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
555
- for (let i = startIdx; i < messageIndex; i++) {
556
- additionalIndices.add(i);
557
- }
558
- }
559
- if (item.withNextMessages) {
560
- const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
561
- for (let i = messageIndex + 1; i <= endIdx; i++) {
562
- additionalIndices.add(i);
563
- }
564
- }
422
+ async batchInsert({ tableName, records }) {
423
+ try {
424
+ if (!this.client) {
425
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
565
426
  }
566
- }
567
- if (additionalIndices.size === 0) {
568
- return records;
569
- }
570
- const originalMatchIds = new Set(include.map((item) => item.id));
571
- const allIndices = /* @__PURE__ */ new Set();
572
- records.forEach((record, index) => {
573
- if (originalMatchIds.has(record.id)) {
574
- allIndices.add(index);
427
+ if (!tableName) {
428
+ throw new Error("tableName is required for batchInsert.");
575
429
  }
576
- });
577
- additionalIndices.forEach((index) => {
578
- allIndices.add(index);
579
- });
580
- return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
581
- }
582
- /**
583
- * Parse message data from LanceDB record format to MastraDBMessage format
584
- */
585
- parseMessageData(data) {
586
- const { thread_id, ...rest } = data;
587
- return {
588
- ...rest,
589
- threadId: thread_id,
590
- content: typeof data.content === "string" ? (() => {
591
- try {
592
- return JSON.parse(data.content);
593
- } catch {
594
- return data.content;
595
- }
596
- })() : data.content,
597
- createdAt: new Date(data.createdAt),
598
- updatedAt: new Date(data.updatedAt)
599
- };
600
- }
601
- async updateMessages(args) {
602
- const { messages } = args;
603
- this.logger.debug("Updating messages", { count: messages.length });
604
- if (!messages.length) {
605
- return [];
430
+ if (!records || records.length === 0) {
431
+ throw new Error("records array is required and cannot be empty for batchInsert.");
432
+ }
433
+ } catch (validationError) {
434
+ throw new MastraError(
435
+ {
436
+ id: createStorageErrorId("LANCE", "BATCH_INSERT", "INVALID_ARGS"),
437
+ domain: ErrorDomain.STORAGE,
438
+ category: ErrorCategory.USER,
439
+ text: validationError.message,
440
+ details: { tableName }
441
+ },
442
+ validationError
443
+ );
606
444
  }
607
- const updatedMessages = [];
608
- const affectedThreadIds = /* @__PURE__ */ new Set();
609
445
  try {
610
- for (const updateData of messages) {
611
- const { id, ...updates } = updateData;
612
- const existingMessage = await this.operations.load({ tableName: TABLE_MESSAGES, keys: { id } });
613
- if (!existingMessage) {
614
- this.logger.warn("Message not found for update", { id });
615
- continue;
616
- }
617
- const existingMsg = this.parseMessageData(existingMessage);
618
- const originalThreadId = existingMsg.threadId;
619
- affectedThreadIds.add(originalThreadId);
620
- const updatePayload = {};
621
- if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
622
- if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
623
- if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
624
- if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
625
- updatePayload.thread_id = updates.threadId;
626
- affectedThreadIds.add(updates.threadId);
627
- }
628
- if (updates.content) {
629
- const existingContent = existingMsg.content;
630
- let newContent = { ...existingContent };
631
- if (updates.content.metadata !== void 0) {
632
- newContent.metadata = {
633
- ...existingContent.metadata || {},
634
- ...updates.content.metadata || {}
635
- };
636
- }
637
- if (updates.content.content !== void 0) {
638
- newContent.content = updates.content.content;
639
- }
640
- if ("parts" in updates.content && updates.content.parts !== void 0) {
641
- newContent.parts = updates.content.parts;
446
+ const table = await this.client.openTable(tableName);
447
+ const primaryId = getPrimaryKeys(tableName);
448
+ const processedRecords = records.map((record) => {
449
+ const processedRecord = { ...record };
450
+ for (const key in processedRecord) {
451
+ if (processedRecord[key] == null) continue;
452
+ if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
453
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
642
454
  }
643
- updatePayload.content = JSON.stringify(newContent);
644
- }
645
- await this.operations.insert({ tableName: TABLE_MESSAGES, record: { id, ...updatePayload } });
646
- const updatedMessage = await this.operations.load({ tableName: TABLE_MESSAGES, keys: { id } });
647
- if (updatedMessage) {
648
- updatedMessages.push(this.parseMessageData(updatedMessage));
649
455
  }
650
- }
651
- for (const threadId of affectedThreadIds) {
652
- await this.operations.insert({
653
- tableName: TABLE_THREADS,
654
- record: { id: threadId, updatedAt: Date.now() }
655
- });
656
- }
657
- return updatedMessages;
456
+ return processedRecord;
457
+ });
458
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
658
459
  } catch (error) {
659
460
  throw new MastraError(
660
461
  {
661
- id: "LANCE_STORE_UPDATE_MESSAGES_FAILED",
462
+ id: createStorageErrorId("LANCE", "BATCH_INSERT", "FAILED"),
662
463
  domain: ErrorDomain.STORAGE,
663
464
  category: ErrorCategory.THIRD_PARTY,
664
- details: { count: messages.length }
465
+ details: { tableName }
665
466
  },
666
467
  error
667
468
  );
668
469
  }
669
470
  }
670
- async getResourceById({ resourceId }) {
471
+ async load({ tableName, keys }) {
671
472
  try {
672
- const resource = await this.operations.load({ tableName: TABLE_RESOURCES, keys: { id: resourceId } });
673
- if (!resource) {
674
- return null;
675
- }
676
- let createdAt;
677
- let updatedAt;
678
- try {
679
- if (resource.createdAt instanceof Date) {
680
- createdAt = resource.createdAt;
681
- } else if (typeof resource.createdAt === "string") {
682
- createdAt = new Date(resource.createdAt);
683
- } else if (typeof resource.createdAt === "number") {
684
- createdAt = new Date(resource.createdAt);
685
- } else {
686
- createdAt = /* @__PURE__ */ new Date();
687
- }
688
- if (isNaN(createdAt.getTime())) {
689
- createdAt = /* @__PURE__ */ new Date();
690
- }
691
- } catch {
692
- createdAt = /* @__PURE__ */ new Date();
693
- }
694
- try {
695
- if (resource.updatedAt instanceof Date) {
696
- updatedAt = resource.updatedAt;
697
- } else if (typeof resource.updatedAt === "string") {
698
- updatedAt = new Date(resource.updatedAt);
699
- } else if (typeof resource.updatedAt === "number") {
700
- updatedAt = new Date(resource.updatedAt);
701
- } else {
702
- updatedAt = /* @__PURE__ */ new Date();
703
- }
704
- if (isNaN(updatedAt.getTime())) {
705
- updatedAt = /* @__PURE__ */ new Date();
706
- }
707
- } catch {
708
- updatedAt = /* @__PURE__ */ new Date();
473
+ if (!this.client) {
474
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
709
475
  }
710
- let workingMemory = resource.workingMemory;
711
- if (workingMemory === null || workingMemory === void 0) {
712
- workingMemory = void 0;
713
- } else if (workingMemory === "") {
714
- workingMemory = "";
715
- } else if (typeof workingMemory === "object") {
716
- workingMemory = JSON.stringify(workingMemory);
476
+ if (!tableName) {
477
+ throw new Error("tableName is required for load.");
717
478
  }
718
- let metadata = resource.metadata;
719
- if (metadata === "" || metadata === null || metadata === void 0) {
720
- metadata = void 0;
721
- } else if (typeof metadata === "string") {
722
- try {
723
- metadata = JSON.parse(metadata);
724
- } catch {
725
- metadata = metadata;
726
- }
479
+ if (!keys || Object.keys(keys).length === 0) {
480
+ throw new Error("keys are required and cannot be empty for load.");
727
481
  }
728
- return {
729
- ...resource,
730
- createdAt,
731
- updatedAt,
732
- workingMemory,
733
- metadata
734
- };
735
- } catch (error) {
482
+ } catch (validationError) {
736
483
  throw new MastraError(
737
484
  {
738
- id: "LANCE_STORE_GET_RESOURCE_BY_ID_FAILED",
485
+ id: createStorageErrorId("LANCE", "LOAD", "INVALID_ARGS"),
739
486
  domain: ErrorDomain.STORAGE,
740
- category: ErrorCategory.THIRD_PARTY
487
+ category: ErrorCategory.USER,
488
+ text: validationError.message,
489
+ details: { tableName }
741
490
  },
742
- error
491
+ validationError
743
492
  );
744
493
  }
745
- }
746
- async saveResource({ resource }) {
747
494
  try {
748
- const record = {
749
- ...resource,
750
- metadata: resource.metadata ? JSON.stringify(resource.metadata) : "",
751
- createdAt: resource.createdAt.getTime(),
752
- // Store as timestamp (milliseconds)
753
- updatedAt: resource.updatedAt.getTime()
754
- // Store as timestamp (milliseconds)
755
- };
756
- const table = await this.client.openTable(TABLE_RESOURCES);
757
- await table.add([record], { mode: "append" });
758
- return resource;
495
+ const table = await this.client.openTable(tableName);
496
+ const tableSchema = await getTableSchema({ tableName, client: this.client });
497
+ const query = table.query();
498
+ if (Object.keys(keys).length > 0) {
499
+ validateKeyTypes(keys, tableSchema);
500
+ const filterConditions = Object.entries(keys).map(([key, value]) => {
501
+ const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
502
+ const quotedKey = isCamelCase ? `\`${key}\`` : key;
503
+ if (typeof value === "string") {
504
+ return `${quotedKey} = '${value}'`;
505
+ } else if (value === null) {
506
+ return `${quotedKey} IS NULL`;
507
+ } else {
508
+ return `${quotedKey} = ${value}`;
509
+ }
510
+ }).join(" AND ");
511
+ this.logger.debug("where clause generated: " + filterConditions);
512
+ query.where(filterConditions);
513
+ }
514
+ const result = await query.limit(1).toArray();
515
+ if (result.length === 0) {
516
+ this.logger.debug("No record found");
517
+ return null;
518
+ }
519
+ return processResultWithTypeConversion(result[0], tableSchema);
759
520
  } catch (error) {
521
+ if (error instanceof MastraError) throw error;
760
522
  throw new MastraError(
761
523
  {
762
- id: "LANCE_STORE_SAVE_RESOURCE_FAILED",
524
+ id: createStorageErrorId("LANCE", "LOAD", "FAILED"),
763
525
  domain: ErrorDomain.STORAGE,
764
- category: ErrorCategory.THIRD_PARTY
526
+ category: ErrorCategory.THIRD_PARTY,
527
+ details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
765
528
  },
766
529
  error
767
530
  );
768
531
  }
769
532
  }
770
- async updateResource({
771
- resourceId,
772
- workingMemory,
773
- metadata
774
- }) {
775
- const maxRetries = 3;
776
- for (let attempt = 0; attempt < maxRetries; attempt++) {
777
- try {
778
- const existingResource = await this.getResourceById({ resourceId });
779
- if (!existingResource) {
780
- const newResource = {
781
- id: resourceId,
782
- workingMemory,
783
- metadata: metadata || {},
784
- createdAt: /* @__PURE__ */ new Date(),
785
- updatedAt: /* @__PURE__ */ new Date()
786
- };
787
- return this.saveResource({ resource: newResource });
788
- }
789
- const updatedResource = {
790
- ...existingResource,
791
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
792
- metadata: {
793
- ...existingResource.metadata,
794
- ...metadata
795
- },
796
- updatedAt: /* @__PURE__ */ new Date()
797
- };
798
- const record = {
799
- id: resourceId,
800
- workingMemory: updatedResource.workingMemory || "",
801
- metadata: updatedResource.metadata ? JSON.stringify(updatedResource.metadata) : "",
802
- updatedAt: updatedResource.updatedAt.getTime()
803
- // Store as timestamp (milliseconds)
804
- };
805
- const table = await this.client.openTable(TABLE_RESOURCES);
806
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
807
- return updatedResource;
808
- } catch (error) {
809
- if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
810
- const delay = Math.pow(2, attempt) * 10;
811
- await new Promise((resolve) => setTimeout(resolve, delay));
812
- continue;
813
- }
814
- throw new MastraError(
815
- {
816
- id: "LANCE_STORE_UPDATE_RESOURCE_FAILED",
817
- domain: ErrorDomain.STORAGE,
818
- category: ErrorCategory.THIRD_PARTY
819
- },
820
- error
821
- );
822
- }
823
- }
824
- throw new Error("Unexpected end of retry loop");
825
- }
826
533
  };
827
- var StoreOperationsLance = class extends StoreOperations {
534
+
535
+ // src/storage/domains/memory/index.ts
536
+ var StoreMemoryLance = class extends MemoryStorage {
828
537
  client;
829
- constructor({ client }) {
538
+ #db;
539
+ constructor(config) {
830
540
  super();
541
+ const client = resolveLanceConfig(config);
831
542
  this.client = client;
832
- }
833
- getDefaultValue(type) {
834
- switch (type) {
835
- case "text":
836
- return "''";
837
- case "timestamp":
838
- return "CURRENT_TIMESTAMP";
839
- case "integer":
840
- case "bigint":
841
- return "0";
842
- case "jsonb":
843
- return "'{}'";
844
- case "uuid":
845
- return "''";
846
- default:
847
- return super.getDefaultValue(type);
848
- }
849
- }
850
- async hasColumn(tableName, columnName) {
851
- const table = await this.client.openTable(tableName);
852
- const schema = await table.schema();
853
- return schema.fields.some((field) => field.name === columnName);
854
- }
855
- translateSchema(schema) {
856
- const fields = Object.entries(schema).map(([name, column]) => {
857
- let arrowType;
858
- switch (column.type.toLowerCase()) {
859
- case "text":
860
- case "uuid":
861
- arrowType = new Utf8();
862
- break;
863
- case "int":
864
- case "integer":
865
- arrowType = new Int32();
866
- break;
867
- case "bigint":
868
- arrowType = new Float64();
869
- break;
870
- case "float":
871
- arrowType = new Float32();
872
- break;
873
- case "jsonb":
874
- case "json":
875
- arrowType = new Utf8();
876
- break;
877
- case "binary":
878
- arrowType = new Binary();
879
- break;
880
- case "timestamp":
881
- arrowType = new Float64();
882
- break;
883
- default:
884
- arrowType = new Utf8();
885
- }
886
- return new Field(name, arrowType, column.nullable ?? true);
543
+ this.#db = new LanceDB({ client });
544
+ }
545
+ async init() {
546
+ await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
547
+ await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
548
+ await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
549
+ await this.#db.alterTable({
550
+ tableName: TABLE_MESSAGES,
551
+ schema: TABLE_SCHEMAS[TABLE_MESSAGES],
552
+ ifNotExists: ["resourceId"]
887
553
  });
888
- return new Schema(fields);
889
554
  }
890
- async createTable({
891
- tableName,
892
- schema
893
- }) {
555
+ async dangerouslyClearAll() {
556
+ await this.#db.clearTable({ tableName: TABLE_THREADS });
557
+ await this.#db.clearTable({ tableName: TABLE_MESSAGES });
558
+ await this.#db.clearTable({ tableName: TABLE_RESOURCES });
559
+ }
560
+ async deleteMessages(messageIds) {
561
+ if (!messageIds || messageIds.length === 0) {
562
+ return;
563
+ }
564
+ this.logger.debug("Deleting messages", { count: messageIds.length });
894
565
  try {
895
- if (!this.client) {
896
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
897
- }
898
- if (!tableName) {
899
- throw new Error("tableName is required for createTable.");
566
+ const threadIds = /* @__PURE__ */ new Set();
567
+ for (const messageId of messageIds) {
568
+ const message = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id: messageId } });
569
+ if (message?.thread_id) {
570
+ threadIds.add(message.thread_id);
571
+ }
900
572
  }
901
- if (!schema) {
902
- throw new Error("schema is required for createTable.");
573
+ const messagesTable = await this.client.openTable(TABLE_MESSAGES);
574
+ const idConditions = messageIds.map((id) => `id = '${this.escapeSql(id)}'`).join(" OR ");
575
+ await messagesTable.delete(idConditions);
576
+ const now = (/* @__PURE__ */ new Date()).getTime();
577
+ const threadsTable = await this.client.openTable(TABLE_THREADS);
578
+ for (const threadId of threadIds) {
579
+ const thread = await this.getThreadById({ threadId });
580
+ if (thread) {
581
+ const record = {
582
+ id: threadId,
583
+ resourceId: thread.resourceId,
584
+ title: thread.title,
585
+ metadata: JSON.stringify(thread.metadata),
586
+ createdAt: new Date(thread.createdAt).getTime(),
587
+ updatedAt: now
588
+ };
589
+ await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
590
+ }
903
591
  }
904
592
  } catch (error) {
905
593
  throw new MastraError(
906
594
  {
907
- id: "STORAGE_LANCE_STORAGE_CREATE_TABLE_INVALID_ARGS",
595
+ id: createStorageErrorId("LANCE", "DELETE_MESSAGES", "FAILED"),
908
596
  domain: ErrorDomain.STORAGE,
909
- category: ErrorCategory.USER,
910
- details: { tableName }
597
+ category: ErrorCategory.THIRD_PARTY,
598
+ details: { count: messageIds.length }
911
599
  },
912
600
  error
913
601
  );
914
602
  }
603
+ }
604
+ // Utility to escape single quotes in SQL strings
605
+ escapeSql(str) {
606
+ return str.replace(/'/g, "''");
607
+ }
608
+ async getThreadById({ threadId }) {
915
609
  try {
916
- const arrowSchema = this.translateSchema(schema);
917
- await this.client.createEmptyTable(tableName, arrowSchema);
918
- } catch (error) {
919
- if (error.message?.includes("already exists")) {
920
- this.logger.debug(`Table '${tableName}' already exists, skipping create`);
921
- return;
610
+ const thread = await this.#db.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
611
+ if (!thread) {
612
+ return null;
922
613
  }
614
+ return {
615
+ ...thread,
616
+ createdAt: new Date(thread.createdAt),
617
+ updatedAt: new Date(thread.updatedAt)
618
+ };
619
+ } catch (error) {
923
620
  throw new MastraError(
924
621
  {
925
- id: "STORAGE_LANCE_STORAGE_CREATE_TABLE_FAILED",
622
+ id: createStorageErrorId("LANCE", "GET_THREAD_BY_ID", "FAILED"),
926
623
  domain: ErrorDomain.STORAGE,
927
- category: ErrorCategory.THIRD_PARTY,
928
- details: { tableName }
624
+ category: ErrorCategory.THIRD_PARTY
929
625
  },
930
626
  error
931
627
  );
932
628
  }
933
629
  }
934
- async dropTable({ tableName }) {
630
+ /**
631
+ * Saves a thread to the database. This function doesn't overwrite existing threads.
632
+ * @param thread - The thread to save
633
+ * @returns The saved thread
634
+ */
635
+ async saveThread({ thread }) {
935
636
  try {
936
- if (!this.client) {
937
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
938
- }
939
- if (!tableName) {
940
- throw new Error("tableName is required for dropTable.");
941
- }
942
- } catch (validationError) {
637
+ const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
638
+ const table = await this.client.openTable(TABLE_THREADS);
639
+ await table.add([record], { mode: "append" });
640
+ return thread;
641
+ } catch (error) {
943
642
  throw new MastraError(
944
643
  {
945
- id: "STORAGE_LANCE_STORAGE_DROP_TABLE_INVALID_ARGS",
644
+ id: createStorageErrorId("LANCE", "SAVE_THREAD", "FAILED"),
946
645
  domain: ErrorDomain.STORAGE,
947
- category: ErrorCategory.USER,
948
- text: validationError.message,
949
- details: { tableName }
646
+ category: ErrorCategory.THIRD_PARTY
950
647
  },
951
- validationError
648
+ error
952
649
  );
953
650
  }
651
+ }
652
+ async updateThread({
653
+ id,
654
+ title,
655
+ metadata
656
+ }) {
657
+ const maxRetries = 5;
658
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
659
+ try {
660
+ const current = await this.getThreadById({ threadId: id });
661
+ if (!current) {
662
+ throw new Error(`Thread with id ${id} not found`);
663
+ }
664
+ const mergedMetadata = { ...current.metadata, ...metadata };
665
+ const record = {
666
+ id,
667
+ title,
668
+ metadata: JSON.stringify(mergedMetadata),
669
+ updatedAt: (/* @__PURE__ */ new Date()).getTime()
670
+ };
671
+ const table = await this.client.openTable(TABLE_THREADS);
672
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
673
+ const updatedThread = await this.getThreadById({ threadId: id });
674
+ if (!updatedThread) {
675
+ throw new Error(`Failed to retrieve updated thread ${id}`);
676
+ }
677
+ return updatedThread;
678
+ } catch (error) {
679
+ if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
680
+ const delay = Math.pow(2, attempt) * 10;
681
+ await new Promise((resolve) => setTimeout(resolve, delay));
682
+ continue;
683
+ }
684
+ throw new MastraError(
685
+ {
686
+ id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
687
+ domain: ErrorDomain.STORAGE,
688
+ category: ErrorCategory.THIRD_PARTY
689
+ },
690
+ error
691
+ );
692
+ }
693
+ }
694
+ throw new MastraError(
695
+ {
696
+ id: createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
697
+ domain: ErrorDomain.STORAGE,
698
+ category: ErrorCategory.THIRD_PARTY
699
+ },
700
+ new Error("All retries exhausted")
701
+ );
702
+ }
703
+ async deleteThread({ threadId }) {
954
704
  try {
955
- await this.client.dropTable(tableName);
705
+ const table = await this.client.openTable(TABLE_THREADS);
706
+ await table.delete(`id = '${threadId}'`);
707
+ const messagesTable = await this.client.openTable(TABLE_MESSAGES);
708
+ await messagesTable.delete(`thread_id = '${threadId}'`);
956
709
  } catch (error) {
957
- if (error.toString().includes("was not found") || error.message?.includes("Table not found")) {
958
- this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
959
- return;
960
- }
961
710
  throw new MastraError(
962
711
  {
963
- id: "STORAGE_LANCE_STORAGE_DROP_TABLE_FAILED",
712
+ id: createStorageErrorId("LANCE", "DELETE_THREAD", "FAILED"),
964
713
  domain: ErrorDomain.STORAGE,
965
- category: ErrorCategory.THIRD_PARTY,
966
- details: { tableName }
714
+ category: ErrorCategory.THIRD_PARTY
967
715
  },
968
716
  error
969
717
  );
970
718
  }
971
719
  }
972
- async alterTable({
973
- tableName,
974
- schema,
975
- ifNotExists
976
- }) {
720
+ normalizeMessage(message) {
721
+ const { thread_id, ...rest } = message;
722
+ return {
723
+ ...rest,
724
+ threadId: thread_id,
725
+ content: typeof message.content === "string" ? (() => {
726
+ try {
727
+ return JSON.parse(message.content);
728
+ } catch {
729
+ return message.content;
730
+ }
731
+ })() : message.content
732
+ };
733
+ }
734
+ async listMessagesById({ messageIds }) {
735
+ if (messageIds.length === 0) return { messages: [] };
977
736
  try {
978
- if (!this.client) {
979
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
980
- }
981
- if (!tableName) {
982
- throw new Error("tableName is required for alterTable.");
983
- }
984
- if (!schema) {
985
- throw new Error("schema is required for alterTable.");
986
- }
987
- if (!ifNotExists || ifNotExists.length === 0) {
988
- this.logger.debug("No columns specified to add in alterTable, skipping.");
989
- return;
990
- }
991
- } catch (validationError) {
992
- throw new MastraError(
993
- {
994
- id: "STORAGE_LANCE_STORAGE_ALTER_TABLE_INVALID_ARGS",
995
- domain: ErrorDomain.STORAGE,
996
- category: ErrorCategory.USER,
997
- text: validationError.message,
998
- details: { tableName }
999
- },
1000
- validationError
737
+ const table = await this.client.openTable(TABLE_MESSAGES);
738
+ const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
739
+ const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
740
+ const messages = processResultWithTypeConversion(
741
+ allRecords,
742
+ await getTableSchema({ tableName: TABLE_MESSAGES, client: this.client })
1001
743
  );
1002
- }
1003
- try {
1004
- const table = await this.client.openTable(tableName);
1005
- const currentSchema = await table.schema();
1006
- const existingFields = new Set(currentSchema.fields.map((f) => f.name));
1007
- const typeMap = {
1008
- text: "string",
1009
- integer: "int",
1010
- bigint: "bigint",
1011
- timestamp: "timestamp",
1012
- jsonb: "string",
1013
- uuid: "string"
1014
- };
1015
- const columnsToAdd = ifNotExists.filter((col) => schema[col] && !existingFields.has(col)).map((col) => {
1016
- const colDef = schema[col];
1017
- return {
1018
- name: col,
1019
- valueSql: colDef?.nullable ? `cast(NULL as ${typeMap[colDef.type ?? "text"]})` : `cast(${this.getDefaultValue(colDef?.type ?? "text")} as ${typeMap[colDef?.type ?? "text"]})`
1020
- };
1021
- });
1022
- if (columnsToAdd.length > 0) {
1023
- await table.addColumns(columnsToAdd);
1024
- this.logger?.info?.(`Added columns [${columnsToAdd.map((c) => c.name).join(", ")}] to table ${tableName}`);
1025
- }
744
+ const list = new MessageList().add(
745
+ messages.map(this.normalizeMessage),
746
+ "memory"
747
+ );
748
+ return { messages: list.get.all.db() };
1026
749
  } catch (error) {
1027
750
  throw new MastraError(
1028
751
  {
1029
- id: "STORAGE_LANCE_STORAGE_ALTER_TABLE_FAILED",
752
+ id: createStorageErrorId("LANCE", "LIST_MESSAGES_BY_ID", "FAILED"),
1030
753
  domain: ErrorDomain.STORAGE,
1031
754
  category: ErrorCategory.THIRD_PARTY,
1032
- details: { tableName }
755
+ details: {
756
+ messageIds: JSON.stringify(messageIds)
757
+ }
1033
758
  },
1034
759
  error
1035
760
  );
1036
761
  }
1037
762
  }
1038
- async clearTable({ tableName }) {
1039
- try {
1040
- if (!this.client) {
1041
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1042
- }
1043
- if (!tableName) {
1044
- throw new Error("tableName is required for clearTable.");
1045
- }
1046
- } catch (validationError) {
763
+ async listMessages(args) {
764
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
765
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
766
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1047
767
  throw new MastraError(
1048
768
  {
1049
- id: "STORAGE_LANCE_STORAGE_CLEAR_TABLE_INVALID_ARGS",
769
+ id: createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_THREAD_ID"),
1050
770
  domain: ErrorDomain.STORAGE,
1051
- category: ErrorCategory.USER,
1052
- text: validationError.message,
1053
- details: { tableName }
771
+ category: ErrorCategory.THIRD_PARTY,
772
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1054
773
  },
1055
- validationError
774
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1056
775
  );
1057
776
  }
777
+ const perPage = normalizePerPage(perPageInput, 40);
778
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1058
779
  try {
1059
- const table = await this.client.openTable(tableName);
1060
- await table.delete("1=1");
780
+ if (page < 0) {
781
+ throw new MastraError(
782
+ {
783
+ id: createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_PAGE"),
784
+ domain: ErrorDomain.STORAGE,
785
+ category: ErrorCategory.USER,
786
+ details: { page }
787
+ },
788
+ new Error("page must be >= 0")
789
+ );
790
+ }
791
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
792
+ const table = await this.client.openTable(TABLE_MESSAGES);
793
+ const threadCondition = threadIds.length === 1 ? `thread_id = '${this.escapeSql(threadIds[0])}'` : `thread_id IN (${threadIds.map((t) => `'${this.escapeSql(t)}'`).join(", ")})`;
794
+ const conditions = [threadCondition];
795
+ if (resourceId) {
796
+ conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
797
+ }
798
+ if (filter?.dateRange?.start) {
799
+ const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
800
+ const startOp = filter.dateRange.startExclusive ? ">" : ">=";
801
+ conditions.push(`\`createdAt\` ${startOp} ${startTime}`);
802
+ }
803
+ if (filter?.dateRange?.end) {
804
+ const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
805
+ const endOp = filter.dateRange.endExclusive ? "<" : "<=";
806
+ conditions.push(`\`createdAt\` ${endOp} ${endTime}`);
807
+ }
808
+ const whereClause = conditions.join(" AND ");
809
+ const total = await table.countRows(whereClause);
810
+ const query = table.query().where(whereClause);
811
+ let allRecords = await query.toArray();
812
+ allRecords.sort((a, b) => {
813
+ const aValue = field === "createdAt" ? a.createdAt : a[field];
814
+ const bValue = field === "createdAt" ? b.createdAt : b[field];
815
+ if (aValue == null && bValue == null) return 0;
816
+ if (aValue == null) return direction === "ASC" ? -1 : 1;
817
+ if (bValue == null) return direction === "ASC" ? 1 : -1;
818
+ if (typeof aValue === "string" && typeof bValue === "string") {
819
+ return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
820
+ }
821
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
822
+ });
823
+ const paginatedRecords = allRecords.slice(offset, offset + perPage);
824
+ const messages = paginatedRecords.map((row) => this.normalizeMessage(row));
825
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
826
+ return {
827
+ messages: [],
828
+ total: 0,
829
+ page,
830
+ perPage: perPageForResponse,
831
+ hasMore: false
832
+ };
833
+ }
834
+ const messageIds = new Set(messages.map((m) => m.id));
835
+ if (include && include.length > 0) {
836
+ const threadIds2 = [...new Set(include.map((item) => item.threadId || threadId))];
837
+ const allThreadMessages = [];
838
+ for (const tid of threadIds2) {
839
+ const threadQuery = table.query().where(`thread_id = '${tid}'`);
840
+ let threadRecords = await threadQuery.toArray();
841
+ allThreadMessages.push(...threadRecords);
842
+ }
843
+ allThreadMessages.sort((a, b) => a.createdAt - b.createdAt);
844
+ const contextMessages = this.processMessagesWithContext(allThreadMessages, include);
845
+ const includedMessages = contextMessages.map((row) => this.normalizeMessage(row));
846
+ for (const includeMsg of includedMessages) {
847
+ if (!messageIds.has(includeMsg.id)) {
848
+ messages.push(includeMsg);
849
+ messageIds.add(includeMsg.id);
850
+ }
851
+ }
852
+ }
853
+ const list = new MessageList().add(messages, "memory");
854
+ let finalMessages = list.get.all.db();
855
+ finalMessages = finalMessages.sort((a, b) => {
856
+ const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
857
+ const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
858
+ if (aValue == null && bValue == null) return 0;
859
+ if (aValue == null) return direction === "ASC" ? -1 : 1;
860
+ if (bValue == null) return direction === "ASC" ? 1 : -1;
861
+ if (typeof aValue === "string" && typeof bValue === "string") {
862
+ return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
863
+ }
864
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
865
+ });
866
+ const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
867
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
868
+ const fetchedAll = perPageInput === false || allThreadMessagesReturned;
869
+ const hasMore = !fetchedAll && offset + perPage < total;
870
+ return {
871
+ messages: finalMessages,
872
+ total,
873
+ page,
874
+ perPage: perPageForResponse,
875
+ hasMore
876
+ };
1061
877
  } catch (error) {
1062
- throw new MastraError(
878
+ const mastraError = new MastraError(
1063
879
  {
1064
- id: "STORAGE_LANCE_STORAGE_CLEAR_TABLE_FAILED",
880
+ id: createStorageErrorId("LANCE", "LIST_MESSAGES", "FAILED"),
1065
881
  domain: ErrorDomain.STORAGE,
1066
882
  category: ErrorCategory.THIRD_PARTY,
1067
- details: { tableName }
883
+ details: {
884
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
885
+ resourceId: resourceId ?? ""
886
+ }
1068
887
  },
1069
888
  error
1070
889
  );
890
+ this.logger?.error?.(mastraError.toString());
891
+ this.logger?.trackException?.(mastraError);
892
+ return {
893
+ messages: [],
894
+ total: 0,
895
+ page,
896
+ perPage: perPageForResponse,
897
+ hasMore: false
898
+ };
1071
899
  }
1072
900
  }
1073
- async insert({ tableName, record }) {
901
+ async saveMessages(args) {
1074
902
  try {
1075
- if (!this.client) {
1076
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
903
+ const { messages } = args;
904
+ if (messages.length === 0) {
905
+ return { messages: [] };
1077
906
  }
1078
- if (!tableName) {
1079
- throw new Error("tableName is required for insert.");
907
+ const threadId = messages[0]?.threadId;
908
+ if (!threadId) {
909
+ throw new Error("Thread ID is required");
1080
910
  }
1081
- if (!record || Object.keys(record).length === 0) {
1082
- throw new Error("record is required and cannot be empty for insert.");
911
+ for (const message of messages) {
912
+ if (!message.id) {
913
+ throw new Error("Message ID is required");
914
+ }
915
+ if (!message.threadId) {
916
+ throw new Error("Thread ID is required for all messages");
917
+ }
918
+ if (message.resourceId === null || message.resourceId === void 0) {
919
+ throw new Error("Resource ID cannot be null or undefined");
920
+ }
921
+ if (!message.content) {
922
+ throw new Error("Message content is required");
923
+ }
1083
924
  }
1084
- } catch (validationError) {
925
+ const transformedMessages = messages.map((message) => {
926
+ const { threadId: threadId2, type, ...rest } = message;
927
+ return {
928
+ ...rest,
929
+ thread_id: threadId2,
930
+ type: type ?? "v2",
931
+ content: JSON.stringify(message.content)
932
+ };
933
+ });
934
+ const table = await this.client.openTable(TABLE_MESSAGES);
935
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
936
+ const threadsTable = await this.client.openTable(TABLE_THREADS);
937
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
938
+ const updateRecord = { id: threadId, updatedAt: currentTime };
939
+ await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([updateRecord]);
940
+ const list = new MessageList().add(messages, "memory");
941
+ return { messages: list.get.all.db() };
942
+ } catch (error) {
1085
943
  throw new MastraError(
1086
944
  {
1087
- id: "STORAGE_LANCE_STORAGE_INSERT_INVALID_ARGS",
945
+ id: createStorageErrorId("LANCE", "SAVE_MESSAGES", "FAILED"),
1088
946
  domain: ErrorDomain.STORAGE,
1089
- category: ErrorCategory.USER,
1090
- text: validationError.message,
1091
- details: { tableName }
947
+ category: ErrorCategory.THIRD_PARTY
1092
948
  },
1093
- validationError
949
+ error
1094
950
  );
1095
951
  }
952
+ }
953
+ async listThreadsByResourceId(args) {
1096
954
  try {
1097
- const table = await this.client.openTable(tableName);
1098
- const primaryId = getPrimaryKeys(tableName);
1099
- const processedRecord = { ...record };
1100
- for (const key in processedRecord) {
1101
- if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
1102
- this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
1103
- processedRecord[key] = JSON.stringify(processedRecord[key]);
1104
- }
955
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
956
+ const perPage = normalizePerPage(perPageInput, 100);
957
+ if (page < 0) {
958
+ throw new MastraError(
959
+ {
960
+ id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
961
+ domain: ErrorDomain.STORAGE,
962
+ category: ErrorCategory.USER,
963
+ details: { page }
964
+ },
965
+ new Error("page must be >= 0")
966
+ );
1105
967
  }
1106
- console.info(await table.schema());
1107
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
968
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
969
+ const { field, direction } = this.parseOrderBy(orderBy);
970
+ const table = await this.client.openTable(TABLE_THREADS);
971
+ const total = await table.countRows(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
972
+ const query = table.query().where(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
973
+ const records = await query.toArray();
974
+ records.sort((a, b) => {
975
+ const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
976
+ const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
977
+ if (aValue == null && bValue == null) return 0;
978
+ if (aValue == null) return direction === "ASC" ? -1 : 1;
979
+ if (bValue == null) return direction === "ASC" ? 1 : -1;
980
+ if (typeof aValue === "string" && typeof bValue === "string") {
981
+ return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
982
+ }
983
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
984
+ });
985
+ const paginatedRecords = records.slice(offset, offset + perPage);
986
+ const schema = await getTableSchema({ tableName: TABLE_THREADS, client: this.client });
987
+ const threads = paginatedRecords.map(
988
+ (record) => processResultWithTypeConversion(record, schema)
989
+ );
990
+ return {
991
+ threads,
992
+ total,
993
+ page,
994
+ perPage: perPageForResponse,
995
+ hasMore: offset + perPage < total
996
+ };
1108
997
  } catch (error) {
1109
998
  throw new MastraError(
1110
999
  {
1111
- id: "STORAGE_LANCE_STORAGE_INSERT_FAILED",
1000
+ id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1112
1001
  domain: ErrorDomain.STORAGE,
1113
- category: ErrorCategory.THIRD_PARTY,
1114
- details: { tableName }
1002
+ category: ErrorCategory.THIRD_PARTY
1115
1003
  },
1116
1004
  error
1117
1005
  );
1118
1006
  }
1119
1007
  }
1120
- async batchInsert({ tableName, records }) {
1121
- try {
1122
- if (!this.client) {
1123
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1008
+ /**
1009
+ * Processes messages to include context messages based on withPreviousMessages and withNextMessages
1010
+ * @param records - The sorted array of records to process
1011
+ * @param include - The array of include specifications with context parameters
1012
+ * @returns The processed array with context messages included
1013
+ */
1014
+ processMessagesWithContext(records, include) {
1015
+ const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
1016
+ if (messagesWithContext.length === 0) {
1017
+ return records;
1018
+ }
1019
+ const messageIndexMap = /* @__PURE__ */ new Map();
1020
+ records.forEach((message, index) => {
1021
+ messageIndexMap.set(message.id, index);
1022
+ });
1023
+ const additionalIndices = /* @__PURE__ */ new Set();
1024
+ for (const item of messagesWithContext) {
1025
+ const messageIndex = messageIndexMap.get(item.id);
1026
+ if (messageIndex !== void 0) {
1027
+ if (item.withPreviousMessages) {
1028
+ const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
1029
+ for (let i = startIdx; i < messageIndex; i++) {
1030
+ additionalIndices.add(i);
1031
+ }
1032
+ }
1033
+ if (item.withNextMessages) {
1034
+ const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
1035
+ for (let i = messageIndex + 1; i <= endIdx; i++) {
1036
+ additionalIndices.add(i);
1037
+ }
1038
+ }
1124
1039
  }
1125
- if (!tableName) {
1126
- throw new Error("tableName is required for batchInsert.");
1040
+ }
1041
+ if (additionalIndices.size === 0) {
1042
+ return records;
1043
+ }
1044
+ const originalMatchIds = new Set(include.map((item) => item.id));
1045
+ const allIndices = /* @__PURE__ */ new Set();
1046
+ records.forEach((record, index) => {
1047
+ if (originalMatchIds.has(record.id)) {
1048
+ allIndices.add(index);
1127
1049
  }
1128
- if (!records || records.length === 0) {
1129
- throw new Error("records array is required and cannot be empty for batchInsert.");
1050
+ });
1051
+ additionalIndices.forEach((index) => {
1052
+ allIndices.add(index);
1053
+ });
1054
+ return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
1055
+ }
1056
+ /**
1057
+ * Parse message data from LanceDB record format to MastraDBMessage format
1058
+ */
1059
+ parseMessageData(data) {
1060
+ const { thread_id, ...rest } = data;
1061
+ return {
1062
+ ...rest,
1063
+ threadId: thread_id,
1064
+ content: typeof data.content === "string" ? (() => {
1065
+ try {
1066
+ return JSON.parse(data.content);
1067
+ } catch {
1068
+ return data.content;
1069
+ }
1070
+ })() : data.content,
1071
+ createdAt: new Date(data.createdAt),
1072
+ updatedAt: new Date(data.updatedAt)
1073
+ };
1074
+ }
1075
+ async updateMessages(args) {
1076
+ const { messages } = args;
1077
+ this.logger.debug("Updating messages", { count: messages.length });
1078
+ if (!messages.length) {
1079
+ return [];
1080
+ }
1081
+ const updatedMessages = [];
1082
+ const affectedThreadIds = /* @__PURE__ */ new Set();
1083
+ try {
1084
+ for (const updateData of messages) {
1085
+ const { id, ...updates } = updateData;
1086
+ const existingMessage = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id } });
1087
+ if (!existingMessage) {
1088
+ this.logger.warn("Message not found for update", { id });
1089
+ continue;
1090
+ }
1091
+ const existingMsg = this.parseMessageData(existingMessage);
1092
+ const originalThreadId = existingMsg.threadId;
1093
+ affectedThreadIds.add(originalThreadId);
1094
+ const updatePayload = {};
1095
+ if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
1096
+ if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
1097
+ if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
1098
+ if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
1099
+ updatePayload.thread_id = updates.threadId;
1100
+ affectedThreadIds.add(updates.threadId);
1101
+ }
1102
+ if (updates.content) {
1103
+ const existingContent = existingMsg.content;
1104
+ let newContent = { ...existingContent };
1105
+ if (updates.content.metadata !== void 0) {
1106
+ newContent.metadata = {
1107
+ ...existingContent.metadata || {},
1108
+ ...updates.content.metadata || {}
1109
+ };
1110
+ }
1111
+ if (updates.content.content !== void 0) {
1112
+ newContent.content = updates.content.content;
1113
+ }
1114
+ if ("parts" in updates.content && updates.content.parts !== void 0) {
1115
+ newContent.parts = updates.content.parts;
1116
+ }
1117
+ updatePayload.content = JSON.stringify(newContent);
1118
+ }
1119
+ await this.#db.insert({ tableName: TABLE_MESSAGES, record: { id, ...updatePayload } });
1120
+ const updatedMessage = await this.#db.load({ tableName: TABLE_MESSAGES, keys: { id } });
1121
+ if (updatedMessage) {
1122
+ updatedMessages.push(this.parseMessageData(updatedMessage));
1123
+ }
1130
1124
  }
1131
- } catch (validationError) {
1125
+ for (const threadId of affectedThreadIds) {
1126
+ await this.#db.insert({
1127
+ tableName: TABLE_THREADS,
1128
+ record: { id: threadId, updatedAt: Date.now() }
1129
+ });
1130
+ }
1131
+ return updatedMessages;
1132
+ } catch (error) {
1132
1133
  throw new MastraError(
1133
1134
  {
1134
- id: "STORAGE_LANCE_STORAGE_BATCH_INSERT_INVALID_ARGS",
1135
+ id: createStorageErrorId("LANCE", "UPDATE_MESSAGES", "FAILED"),
1135
1136
  domain: ErrorDomain.STORAGE,
1136
- category: ErrorCategory.USER,
1137
- text: validationError.message,
1138
- details: { tableName }
1137
+ category: ErrorCategory.THIRD_PARTY,
1138
+ details: { count: messages.length }
1139
1139
  },
1140
- validationError
1140
+ error
1141
1141
  );
1142
1142
  }
1143
+ }
1144
+ async getResourceById({ resourceId }) {
1143
1145
  try {
1144
- const table = await this.client.openTable(tableName);
1145
- const primaryId = getPrimaryKeys(tableName);
1146
- const processedRecords = records.map((record) => {
1147
- const processedRecord = { ...record };
1148
- for (const key in processedRecord) {
1149
- if (processedRecord[key] == null) continue;
1150
- if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
1151
- processedRecord[key] = JSON.stringify(processedRecord[key]);
1152
- }
1146
+ const resource = await this.#db.load({ tableName: TABLE_RESOURCES, keys: { id: resourceId } });
1147
+ if (!resource) {
1148
+ return null;
1149
+ }
1150
+ let createdAt;
1151
+ let updatedAt;
1152
+ try {
1153
+ if (resource.createdAt instanceof Date) {
1154
+ createdAt = resource.createdAt;
1155
+ } else if (typeof resource.createdAt === "string") {
1156
+ createdAt = new Date(resource.createdAt);
1157
+ } else if (typeof resource.createdAt === "number") {
1158
+ createdAt = new Date(resource.createdAt);
1159
+ } else {
1160
+ createdAt = /* @__PURE__ */ new Date();
1161
+ }
1162
+ if (isNaN(createdAt.getTime())) {
1163
+ createdAt = /* @__PURE__ */ new Date();
1164
+ }
1165
+ } catch {
1166
+ createdAt = /* @__PURE__ */ new Date();
1167
+ }
1168
+ try {
1169
+ if (resource.updatedAt instanceof Date) {
1170
+ updatedAt = resource.updatedAt;
1171
+ } else if (typeof resource.updatedAt === "string") {
1172
+ updatedAt = new Date(resource.updatedAt);
1173
+ } else if (typeof resource.updatedAt === "number") {
1174
+ updatedAt = new Date(resource.updatedAt);
1175
+ } else {
1176
+ updatedAt = /* @__PURE__ */ new Date();
1153
1177
  }
1154
- return processedRecord;
1155
- });
1156
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
1157
- } catch (error) {
1158
- throw new MastraError(
1159
- {
1160
- id: "STORAGE_LANCE_STORAGE_BATCH_INSERT_FAILED",
1161
- domain: ErrorDomain.STORAGE,
1162
- category: ErrorCategory.THIRD_PARTY,
1163
- details: { tableName }
1164
- },
1165
- error
1166
- );
1167
- }
1168
- }
1169
- async load({ tableName, keys }) {
1170
- try {
1171
- if (!this.client) {
1172
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1178
+ if (isNaN(updatedAt.getTime())) {
1179
+ updatedAt = /* @__PURE__ */ new Date();
1180
+ }
1181
+ } catch {
1182
+ updatedAt = /* @__PURE__ */ new Date();
1173
1183
  }
1174
- if (!tableName) {
1175
- throw new Error("tableName is required for load.");
1184
+ let workingMemory = resource.workingMemory;
1185
+ if (workingMemory === null || workingMemory === void 0) {
1186
+ workingMemory = void 0;
1187
+ } else if (workingMemory === "") {
1188
+ workingMemory = "";
1189
+ } else if (typeof workingMemory === "object") {
1190
+ workingMemory = JSON.stringify(workingMemory);
1176
1191
  }
1177
- if (!keys || Object.keys(keys).length === 0) {
1178
- throw new Error("keys are required and cannot be empty for load.");
1192
+ let metadata = resource.metadata;
1193
+ if (metadata === "" || metadata === null || metadata === void 0) {
1194
+ metadata = void 0;
1195
+ } else if (typeof metadata === "string") {
1196
+ try {
1197
+ metadata = JSON.parse(metadata);
1198
+ } catch {
1199
+ metadata = metadata;
1200
+ }
1179
1201
  }
1180
- } catch (validationError) {
1202
+ return {
1203
+ ...resource,
1204
+ createdAt,
1205
+ updatedAt,
1206
+ workingMemory,
1207
+ metadata
1208
+ };
1209
+ } catch (error) {
1181
1210
  throw new MastraError(
1182
1211
  {
1183
- id: "STORAGE_LANCE_STORAGE_LOAD_INVALID_ARGS",
1212
+ id: createStorageErrorId("LANCE", "GET_RESOURCE_BY_ID", "FAILED"),
1184
1213
  domain: ErrorDomain.STORAGE,
1185
- category: ErrorCategory.USER,
1186
- text: validationError.message,
1187
- details: { tableName }
1214
+ category: ErrorCategory.THIRD_PARTY
1188
1215
  },
1189
- validationError
1216
+ error
1190
1217
  );
1191
1218
  }
1219
+ }
1220
+ async saveResource({ resource }) {
1192
1221
  try {
1193
- const table = await this.client.openTable(tableName);
1194
- const tableSchema = await getTableSchema({ tableName, client: this.client });
1195
- const query = table.query();
1196
- if (Object.keys(keys).length > 0) {
1197
- validateKeyTypes(keys, tableSchema);
1198
- const filterConditions = Object.entries(keys).map(([key, value]) => {
1199
- const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
1200
- const quotedKey = isCamelCase ? `\`${key}\`` : key;
1201
- if (typeof value === "string") {
1202
- return `${quotedKey} = '${value}'`;
1203
- } else if (value === null) {
1204
- return `${quotedKey} IS NULL`;
1205
- } else {
1206
- return `${quotedKey} = ${value}`;
1207
- }
1208
- }).join(" AND ");
1209
- this.logger.debug("where clause generated: " + filterConditions);
1210
- query.where(filterConditions);
1211
- }
1212
- const result = await query.limit(1).toArray();
1213
- if (result.length === 0) {
1214
- this.logger.debug("No record found");
1215
- return null;
1216
- }
1217
- return processResultWithTypeConversion(result[0], tableSchema);
1222
+ const record = {
1223
+ ...resource,
1224
+ metadata: resource.metadata ? JSON.stringify(resource.metadata) : "",
1225
+ createdAt: resource.createdAt.getTime(),
1226
+ // Store as timestamp (milliseconds)
1227
+ updatedAt: resource.updatedAt.getTime()
1228
+ // Store as timestamp (milliseconds)
1229
+ };
1230
+ const table = await this.client.openTable(TABLE_RESOURCES);
1231
+ await table.add([record], { mode: "append" });
1232
+ return resource;
1218
1233
  } catch (error) {
1219
- if (error instanceof MastraError) throw error;
1220
1234
  throw new MastraError(
1221
1235
  {
1222
- id: "STORAGE_LANCE_STORAGE_LOAD_FAILED",
1236
+ id: createStorageErrorId("LANCE", "SAVE_RESOURCE", "FAILED"),
1223
1237
  domain: ErrorDomain.STORAGE,
1224
- category: ErrorCategory.THIRD_PARTY,
1225
- details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
1238
+ category: ErrorCategory.THIRD_PARTY
1226
1239
  },
1227
1240
  error
1228
1241
  );
1229
1242
  }
1230
1243
  }
1244
+ async updateResource({
1245
+ resourceId,
1246
+ workingMemory,
1247
+ metadata
1248
+ }) {
1249
+ const maxRetries = 3;
1250
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1251
+ try {
1252
+ const existingResource = await this.getResourceById({ resourceId });
1253
+ if (!existingResource) {
1254
+ const newResource = {
1255
+ id: resourceId,
1256
+ workingMemory,
1257
+ metadata: metadata || {},
1258
+ createdAt: /* @__PURE__ */ new Date(),
1259
+ updatedAt: /* @__PURE__ */ new Date()
1260
+ };
1261
+ return this.saveResource({ resource: newResource });
1262
+ }
1263
+ const updatedResource = {
1264
+ ...existingResource,
1265
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1266
+ metadata: {
1267
+ ...existingResource.metadata,
1268
+ ...metadata
1269
+ },
1270
+ updatedAt: /* @__PURE__ */ new Date()
1271
+ };
1272
+ const record = {
1273
+ id: resourceId,
1274
+ workingMemory: updatedResource.workingMemory || "",
1275
+ metadata: updatedResource.metadata ? JSON.stringify(updatedResource.metadata) : "",
1276
+ updatedAt: updatedResource.updatedAt.getTime()
1277
+ // Store as timestamp (milliseconds)
1278
+ };
1279
+ const table = await this.client.openTable(TABLE_RESOURCES);
1280
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
1281
+ return updatedResource;
1282
+ } catch (error) {
1283
+ if (error.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
1284
+ const delay = Math.pow(2, attempt) * 10;
1285
+ await new Promise((resolve) => setTimeout(resolve, delay));
1286
+ continue;
1287
+ }
1288
+ throw new MastraError(
1289
+ {
1290
+ id: createStorageErrorId("LANCE", "UPDATE_RESOURCE", "FAILED"),
1291
+ domain: ErrorDomain.STORAGE,
1292
+ category: ErrorCategory.THIRD_PARTY
1293
+ },
1294
+ error
1295
+ );
1296
+ }
1297
+ }
1298
+ throw new Error("Unexpected end of retry loop");
1299
+ }
1231
1300
  };
1232
1301
  var StoreScoresLance = class extends ScoresStorage {
1233
1302
  client;
1234
- constructor({ client }) {
1303
+ #db;
1304
+ constructor(config) {
1235
1305
  super();
1306
+ const client = resolveLanceConfig(config);
1236
1307
  this.client = client;
1308
+ this.#db = new LanceDB({ client });
1309
+ }
1310
+ async init() {
1311
+ await this.#db.createTable({ tableName: TABLE_SCORERS, schema: SCORERS_SCHEMA });
1312
+ await this.#db.alterTable({
1313
+ tableName: TABLE_SCORERS,
1314
+ schema: SCORERS_SCHEMA,
1315
+ ifNotExists: ["spanId", "requestContext"]
1316
+ });
1317
+ }
1318
+ async dangerouslyClearAll() {
1319
+ await this.#db.clearTable({ tableName: TABLE_SCORERS });
1237
1320
  }
1238
1321
  async saveScore(score) {
1239
1322
  let validatedScore;
@@ -1242,37 +1325,47 @@ var StoreScoresLance = class extends ScoresStorage {
1242
1325
  } catch (error) {
1243
1326
  throw new MastraError(
1244
1327
  {
1245
- id: "LANCE_STORAGE_SAVE_SCORE_FAILED",
1328
+ id: createStorageErrorId("LANCE", "SAVE_SCORE", "VALIDATION_FAILED"),
1246
1329
  text: "Failed to save score in LanceStorage",
1247
1330
  domain: ErrorDomain.STORAGE,
1248
- category: ErrorCategory.THIRD_PARTY
1331
+ category: ErrorCategory.USER,
1332
+ details: {
1333
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
1334
+ entityId: score.entityId ?? "unknown",
1335
+ entityType: score.entityType ?? "unknown",
1336
+ traceId: score.traceId ?? "",
1337
+ spanId: score.spanId ?? ""
1338
+ }
1249
1339
  },
1250
1340
  error
1251
1341
  );
1252
1342
  }
1343
+ const id = crypto.randomUUID();
1344
+ const now = /* @__PURE__ */ new Date();
1253
1345
  try {
1254
- const id = crypto.randomUUID();
1255
1346
  const table = await this.client.openTable(TABLE_SCORERS);
1256
1347
  const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1257
1348
  const allowedFields = new Set(schema.fields.map((f) => f.name));
1258
1349
  const filteredScore = {};
1259
- Object.keys(validatedScore).forEach((key) => {
1350
+ for (const key of Object.keys(validatedScore)) {
1260
1351
  if (allowedFields.has(key)) {
1261
- filteredScore[key] = score[key];
1352
+ filteredScore[key] = validatedScore[key];
1262
1353
  }
1263
- });
1354
+ }
1264
1355
  for (const key in filteredScore) {
1265
1356
  if (filteredScore[key] !== null && typeof filteredScore[key] === "object" && !(filteredScore[key] instanceof Date)) {
1266
1357
  filteredScore[key] = JSON.stringify(filteredScore[key]);
1267
1358
  }
1268
1359
  }
1269
1360
  filteredScore.id = id;
1361
+ filteredScore.createdAt = now;
1362
+ filteredScore.updatedAt = now;
1270
1363
  await table.add([filteredScore], { mode: "append" });
1271
- return { score };
1364
+ return { score: { ...validatedScore, id, createdAt: now, updatedAt: now } };
1272
1365
  } catch (error) {
1273
1366
  throw new MastraError(
1274
1367
  {
1275
- id: "LANCE_STORAGE_SAVE_SCORE_FAILED",
1368
+ id: createStorageErrorId("LANCE", "SAVE_SCORE", "FAILED"),
1276
1369
  text: "Failed to save score in LanceStorage",
1277
1370
  domain: ErrorDomain.STORAGE,
1278
1371
  category: ErrorCategory.THIRD_PARTY,
@@ -1288,12 +1381,11 @@ var StoreScoresLance = class extends ScoresStorage {
1288
1381
  const query = table.query().where(`id = '${id}'`).limit(1);
1289
1382
  const records = await query.toArray();
1290
1383
  if (records.length === 0) return null;
1291
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1292
- return processResultWithTypeConversion(records[0], schema);
1384
+ return await this.transformScoreRow(records[0]);
1293
1385
  } catch (error) {
1294
1386
  throw new MastraError(
1295
1387
  {
1296
- id: "LANCE_STORAGE_GET_SCORE_BY_ID_FAILED",
1388
+ id: createStorageErrorId("LANCE", "GET_SCORE_BY_ID", "FAILED"),
1297
1389
  text: "Failed to get score by id in LanceStorage",
1298
1390
  domain: ErrorDomain.STORAGE,
1299
1391
  category: ErrorCategory.THIRD_PARTY,
@@ -1303,6 +1395,22 @@ var StoreScoresLance = class extends ScoresStorage {
1303
1395
  );
1304
1396
  }
1305
1397
  }
1398
+ /**
1399
+ * LanceDB-specific score row transformation.
1400
+ *
1401
+ * Note: This implementation does NOT use coreTransformScoreRow because:
1402
+ * 1. LanceDB stores schema information in the table itself (requires async fetch)
1403
+ * 2. Uses processResultWithTypeConversion utility for LanceDB-specific type handling
1404
+ */
1405
+ async transformScoreRow(row) {
1406
+ const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1407
+ const transformed = processResultWithTypeConversion(row, schema);
1408
+ return {
1409
+ ...transformed,
1410
+ createdAt: row.createdAt,
1411
+ updatedAt: row.updatedAt
1412
+ };
1413
+ }
1306
1414
  async listScoresByScorerId({
1307
1415
  scorerId,
1308
1416
  pagination,
@@ -1343,8 +1451,7 @@ var StoreScoresLance = class extends ScoresStorage {
1343
1451
  if (start > 0) query = query.offset(start);
1344
1452
  }
1345
1453
  const records = await query.toArray();
1346
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1347
- const scores = processResultWithTypeConversion(records, schema);
1454
+ const scores = await Promise.all(records.map(async (record) => await this.transformScoreRow(record)));
1348
1455
  return {
1349
1456
  pagination: {
1350
1457
  page,
@@ -1357,7 +1464,7 @@ var StoreScoresLance = class extends ScoresStorage {
1357
1464
  } catch (error) {
1358
1465
  throw new MastraError(
1359
1466
  {
1360
- id: "LANCE_STORAGE_GET_SCORES_BY_SCORER_ID_FAILED",
1467
+ id: createStorageErrorId("LANCE", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
1361
1468
  text: "Failed to get scores by scorerId in LanceStorage",
1362
1469
  domain: ErrorDomain.STORAGE,
1363
1470
  category: ErrorCategory.THIRD_PARTY,
@@ -1385,8 +1492,7 @@ var StoreScoresLance = class extends ScoresStorage {
1385
1492
  if (start > 0) query = query.offset(start);
1386
1493
  }
1387
1494
  const records = await query.toArray();
1388
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1389
- const scores = processResultWithTypeConversion(records, schema);
1495
+ const scores = await Promise.all(records.map(async (record) => await this.transformScoreRow(record)));
1390
1496
  return {
1391
1497
  pagination: {
1392
1498
  page,
@@ -1399,7 +1505,7 @@ var StoreScoresLance = class extends ScoresStorage {
1399
1505
  } catch (error) {
1400
1506
  throw new MastraError(
1401
1507
  {
1402
- id: "LANCE_STORAGE_GET_SCORES_BY_RUN_ID_FAILED",
1508
+ id: createStorageErrorId("LANCE", "LIST_SCORES_BY_RUN_ID", "FAILED"),
1403
1509
  text: "Failed to get scores by runId in LanceStorage",
1404
1510
  domain: ErrorDomain.STORAGE,
1405
1511
  category: ErrorCategory.THIRD_PARTY,
@@ -1428,8 +1534,7 @@ var StoreScoresLance = class extends ScoresStorage {
1428
1534
  if (start > 0) query = query.offset(start);
1429
1535
  }
1430
1536
  const records = await query.toArray();
1431
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1432
- const scores = processResultWithTypeConversion(records, schema);
1537
+ const scores = await Promise.all(records.map(async (record) => await this.transformScoreRow(record)));
1433
1538
  return {
1434
1539
  pagination: {
1435
1540
  page,
@@ -1442,7 +1547,7 @@ var StoreScoresLance = class extends ScoresStorage {
1442
1547
  } catch (error) {
1443
1548
  throw new MastraError(
1444
1549
  {
1445
- id: "LANCE_STORAGE_GET_SCORES_BY_ENTITY_ID_FAILED",
1550
+ id: createStorageErrorId("LANCE", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
1446
1551
  text: "Failed to get scores by entityId and entityType in LanceStorage",
1447
1552
  domain: ErrorDomain.STORAGE,
1448
1553
  category: ErrorCategory.THIRD_PARTY,
@@ -1471,8 +1576,7 @@ var StoreScoresLance = class extends ScoresStorage {
1471
1576
  if (start > 0) query = query.offset(start);
1472
1577
  }
1473
1578
  const records = await query.toArray();
1474
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
1475
- const scores = processResultWithTypeConversion(records, schema);
1579
+ const scores = await Promise.all(records.map(async (record) => await this.transformScoreRow(record)));
1476
1580
  return {
1477
1581
  pagination: {
1478
1582
  page,
@@ -1485,7 +1589,7 @@ var StoreScoresLance = class extends ScoresStorage {
1485
1589
  } catch (error) {
1486
1590
  throw new MastraError(
1487
1591
  {
1488
- id: "LANCE_STORAGE_GET_SCORES_BY_SPAN_FAILED",
1592
+ id: createStorageErrorId("LANCE", "LIST_SCORES_BY_SPAN", "FAILED"),
1489
1593
  text: "Failed to get scores by traceId and spanId in LanceStorage",
1490
1594
  domain: ErrorDomain.STORAGE,
1491
1595
  category: ErrorCategory.THIRD_PARTY,
@@ -1496,6 +1600,9 @@ var StoreScoresLance = class extends ScoresStorage {
1496
1600
  }
1497
1601
  }
1498
1602
  };
1603
+ function escapeSql(str) {
1604
+ return str.replace(/'/g, "''");
1605
+ }
1499
1606
  function parseWorkflowRun(row) {
1500
1607
  let parsedSnapshot = row.snapshot;
1501
1608
  if (typeof parsedSnapshot === "string") {
@@ -1516,42 +1623,88 @@ function parseWorkflowRun(row) {
1516
1623
  }
1517
1624
  var StoreWorkflowsLance = class extends WorkflowsStorage {
1518
1625
  client;
1519
- constructor({ client }) {
1626
+ #db;
1627
+ constructor(config) {
1520
1628
  super();
1629
+ const client = resolveLanceConfig(config);
1521
1630
  this.client = client;
1631
+ this.#db = new LanceDB({ client });
1632
+ }
1633
+ async init() {
1634
+ const schema = TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT];
1635
+ await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema });
1636
+ await this.#db.alterTable({
1637
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
1638
+ schema,
1639
+ ifNotExists: ["resourceId"]
1640
+ });
1522
1641
  }
1523
- updateWorkflowResults({
1524
- // workflowName,
1525
- // runId,
1526
- // stepId,
1527
- // result,
1528
- // requestContext,
1642
+ async dangerouslyClearAll() {
1643
+ await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
1644
+ }
1645
+ async updateWorkflowResults({
1646
+ workflowName,
1647
+ runId,
1648
+ stepId,
1649
+ result,
1650
+ requestContext
1529
1651
  }) {
1530
- throw new Error("Method not implemented.");
1652
+ let snapshot = await this.loadWorkflowSnapshot({ workflowName, runId });
1653
+ if (!snapshot) {
1654
+ snapshot = {
1655
+ context: {},
1656
+ activePaths: [],
1657
+ timestamp: Date.now(),
1658
+ suspendedPaths: {},
1659
+ activeStepsPath: {},
1660
+ resumeLabels: {},
1661
+ serializedStepGraph: [],
1662
+ status: "pending",
1663
+ value: {},
1664
+ waitingPaths: {},
1665
+ runId,
1666
+ requestContext: {}
1667
+ };
1668
+ }
1669
+ snapshot.context[stepId] = result;
1670
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
1671
+ await this.persistWorkflowSnapshot({ workflowName, runId, snapshot });
1672
+ return snapshot.context;
1531
1673
  }
1532
- updateWorkflowState({
1533
- // workflowName,
1534
- // runId,
1535
- // opts,
1674
+ async updateWorkflowState({
1675
+ workflowName,
1676
+ runId,
1677
+ opts
1536
1678
  }) {
1537
- throw new Error("Method not implemented.");
1679
+ const snapshot = await this.loadWorkflowSnapshot({ workflowName, runId });
1680
+ if (!snapshot) {
1681
+ return void 0;
1682
+ }
1683
+ if (!snapshot.context) {
1684
+ throw new Error(`Snapshot not found for runId ${runId}`);
1685
+ }
1686
+ const updatedSnapshot = { ...snapshot, ...opts };
1687
+ await this.persistWorkflowSnapshot({ workflowName, runId, snapshot: updatedSnapshot });
1688
+ return updatedSnapshot;
1538
1689
  }
1539
1690
  async persistWorkflowSnapshot({
1540
1691
  workflowName,
1541
1692
  runId,
1542
1693
  resourceId,
1543
- snapshot
1694
+ snapshot,
1695
+ createdAt,
1696
+ updatedAt
1544
1697
  }) {
1545
1698
  try {
1546
1699
  const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
1547
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
1700
+ const query = table.query().where(`workflow_name = '${escapeSql(workflowName)}' AND run_id = '${escapeSql(runId)}'`);
1548
1701
  const records = await query.toArray();
1549
- let createdAt;
1550
- const now = Date.now();
1702
+ let createdAtValue;
1703
+ const now = createdAt?.getTime() ?? Date.now();
1551
1704
  if (records.length > 0) {
1552
- createdAt = records[0].createdAt ?? now;
1705
+ createdAtValue = records[0].createdAt ?? now;
1553
1706
  } else {
1554
- createdAt = now;
1707
+ createdAtValue = now;
1555
1708
  }
1556
1709
  const { status, value, ...rest } = snapshot;
1557
1710
  const record = {
@@ -1560,14 +1713,14 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1560
1713
  resourceId,
1561
1714
  snapshot: JSON.stringify({ status, value, ...rest }),
1562
1715
  // this is to ensure status is always just before value, for when querying the db by status
1563
- createdAt,
1564
- updatedAt: now
1716
+ createdAt: createdAtValue,
1717
+ updatedAt: updatedAt ?? now
1565
1718
  };
1566
1719
  await table.mergeInsert(["workflow_name", "run_id"]).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
1567
1720
  } catch (error) {
1568
1721
  throw new MastraError(
1569
1722
  {
1570
- id: "LANCE_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
1723
+ id: createStorageErrorId("LANCE", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
1571
1724
  domain: ErrorDomain.STORAGE,
1572
1725
  category: ErrorCategory.THIRD_PARTY,
1573
1726
  details: { workflowName, runId }
@@ -1582,13 +1735,13 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1582
1735
  }) {
1583
1736
  try {
1584
1737
  const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
1585
- const query = table.query().where(`workflow_name = '${workflowName}' AND run_id = '${runId}'`);
1738
+ const query = table.query().where(`workflow_name = '${escapeSql(workflowName)}' AND run_id = '${escapeSql(runId)}'`);
1586
1739
  const records = await query.toArray();
1587
1740
  return records.length > 0 ? JSON.parse(records[0].snapshot) : null;
1588
1741
  } catch (error) {
1589
1742
  throw new MastraError(
1590
1743
  {
1591
- id: "LANCE_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
1744
+ id: createStorageErrorId("LANCE", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
1592
1745
  domain: ErrorDomain.STORAGE,
1593
1746
  category: ErrorCategory.THIRD_PARTY,
1594
1747
  details: { workflowName, runId }
@@ -1600,9 +1753,9 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1600
1753
  async getWorkflowRunById(args) {
1601
1754
  try {
1602
1755
  const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
1603
- let whereClause = `run_id = '${args.runId}'`;
1756
+ let whereClause = `run_id = '${escapeSql(args.runId)}'`;
1604
1757
  if (args.workflowName) {
1605
- whereClause += ` AND workflow_name = '${args.workflowName}'`;
1758
+ whereClause += ` AND workflow_name = '${escapeSql(args.workflowName)}'`;
1606
1759
  }
1607
1760
  const query = table.query().where(whereClause);
1608
1761
  const records = await query.toArray();
@@ -1612,7 +1765,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1612
1765
  } catch (error) {
1613
1766
  throw new MastraError(
1614
1767
  {
1615
- id: "LANCE_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1768
+ id: createStorageErrorId("LANCE", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
1616
1769
  domain: ErrorDomain.STORAGE,
1617
1770
  category: ErrorCategory.THIRD_PARTY,
1618
1771
  details: { runId: args.runId, workflowName: args.workflowName ?? "" }
@@ -1621,20 +1774,37 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1621
1774
  );
1622
1775
  }
1623
1776
  }
1777
+ async deleteWorkflowRunById({ runId, workflowName }) {
1778
+ try {
1779
+ const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
1780
+ const whereClause = `run_id = '${escapeSql(runId)}' AND workflow_name = '${escapeSql(workflowName)}'`;
1781
+ await table.delete(whereClause);
1782
+ } catch (error) {
1783
+ throw new MastraError(
1784
+ {
1785
+ id: createStorageErrorId("LANCE", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
1786
+ domain: ErrorDomain.STORAGE,
1787
+ category: ErrorCategory.THIRD_PARTY,
1788
+ details: { runId, workflowName }
1789
+ },
1790
+ error
1791
+ );
1792
+ }
1793
+ }
1624
1794
  async listWorkflowRuns(args) {
1625
1795
  try {
1626
1796
  const table = await this.client.openTable(TABLE_WORKFLOW_SNAPSHOT);
1627
1797
  let query = table.query();
1628
1798
  const conditions = [];
1629
1799
  if (args?.workflowName) {
1630
- conditions.push(`workflow_name = '${args.workflowName.replace(/'/g, "''")}'`);
1800
+ conditions.push(`workflow_name = '${escapeSql(args.workflowName)}'`);
1631
1801
  }
1632
1802
  if (args?.status) {
1633
1803
  const escapedStatus = args.status.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/%/g, "\\%").replace(/_/g, "\\_");
1634
1804
  conditions.push(`\`snapshot\` LIKE '%"status":"${escapedStatus}","value"%'`);
1635
1805
  }
1636
1806
  if (args?.resourceId) {
1637
- conditions.push(`\`resourceId\` = '${args.resourceId}'`);
1807
+ conditions.push(`\`resourceId\` = '${escapeSql(args.resourceId)}'`);
1638
1808
  }
1639
1809
  if (args?.fromDate instanceof Date) {
1640
1810
  conditions.push(`\`createdAt\` >= ${args.fromDate.getTime()}`);
@@ -1654,7 +1824,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1654
1824
  if (args.page < 0 || !Number.isInteger(args.page)) {
1655
1825
  throw new MastraError(
1656
1826
  {
1657
- id: "LANCE_STORE_INVALID_PAGINATION_PARAMS",
1827
+ id: createStorageErrorId("LANCE", "LIST_WORKFLOW_RUNS", "INVALID_PAGINATION"),
1658
1828
  domain: ErrorDomain.STORAGE,
1659
1829
  category: ErrorCategory.USER,
1660
1830
  details: { page: args.page, perPage: args.perPage }
@@ -1674,7 +1844,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1674
1844
  } catch (error) {
1675
1845
  throw new MastraError(
1676
1846
  {
1677
- id: "LANCE_STORE_LIST_WORKFLOW_RUNS_FAILED",
1847
+ id: createStorageErrorId("LANCE", "LIST_WORKFLOW_RUNS", "FAILED"),
1678
1848
  domain: ErrorDomain.STORAGE,
1679
1849
  category: ErrorCategory.THIRD_PARTY,
1680
1850
  details: { resourceId: args?.resourceId ?? "", workflowName: args?.workflowName ?? "" }
@@ -1694,7 +1864,8 @@ var LanceStorage = class _LanceStorage extends MastraStorage {
1694
1864
  * @param id The unique identifier for this storage instance
1695
1865
  * @param name The name for this storage instance
1696
1866
  * @param uri The URI to connect to LanceDB
1697
- * @param options connection options
1867
+ * @param connectionOptions connection options for LanceDB
1868
+ * @param storageOptions storage options including disableInit
1698
1869
  *
1699
1870
  * Usage:
1700
1871
  *
@@ -1712,245 +1883,73 @@ var LanceStorage = class _LanceStorage extends MastraStorage {
1712
1883
  * ```ts
1713
1884
  * const store = await LanceStorage.create('my-storage-id', 'MyStorage', 's3://bucket/db', { storageOptions: { timeout: '60s' } });
1714
1885
  * ```
1886
+ *
1887
+ * Disable auto-init for runtime (after CI/CD has run migrations)
1888
+ * ```ts
1889
+ * const store = await LanceStorage.create('my-storage-id', 'MyStorage', '/path/to/db', undefined, { disableInit: true });
1890
+ * ```
1715
1891
  */
1716
- static async create(id, name, uri, options) {
1717
- const instance = new _LanceStorage(id, name);
1892
+ static async create(id, name, uri, connectionOptions, storageOptions) {
1893
+ const instance = new _LanceStorage(id, name, storageOptions?.disableInit);
1718
1894
  try {
1719
- instance.lanceClient = await connect(uri, options);
1720
- const operations = new StoreOperationsLance({ client: instance.lanceClient });
1895
+ instance.lanceClient = await connect(uri, connectionOptions);
1721
1896
  instance.stores = {
1722
- operations: new StoreOperationsLance({ client: instance.lanceClient }),
1723
1897
  workflows: new StoreWorkflowsLance({ client: instance.lanceClient }),
1724
1898
  scores: new StoreScoresLance({ client: instance.lanceClient }),
1725
- memory: new StoreMemoryLance({ client: instance.lanceClient, operations })
1899
+ memory: new StoreMemoryLance({ client: instance.lanceClient })
1726
1900
  };
1727
- return instance;
1728
- } catch (e) {
1729
- throw new MastraError(
1730
- {
1731
- id: "STORAGE_LANCE_STORAGE_CONNECT_FAILED",
1732
- domain: ErrorDomain.STORAGE,
1733
- category: ErrorCategory.THIRD_PARTY,
1734
- text: `Failed to connect to LanceDB: ${e.message || e}`,
1735
- details: { uri, optionsProvided: !!options }
1736
- },
1737
- e
1738
- );
1739
- }
1740
- }
1741
- /**
1742
- * @internal
1743
- * Private constructor to enforce using the create factory method
1744
- */
1745
- constructor(id, name) {
1746
- super({ id, name });
1747
- const operations = new StoreOperationsLance({ client: this.lanceClient });
1748
- this.stores = {
1749
- operations: new StoreOperationsLance({ client: this.lanceClient }),
1750
- workflows: new StoreWorkflowsLance({ client: this.lanceClient }),
1751
- scores: new StoreScoresLance({ client: this.lanceClient }),
1752
- memory: new StoreMemoryLance({ client: this.lanceClient, operations })
1753
- };
1754
- }
1755
- async createTable({
1756
- tableName,
1757
- schema
1758
- }) {
1759
- return this.stores.operations.createTable({ tableName, schema });
1760
- }
1761
- async dropTable({ tableName }) {
1762
- return this.stores.operations.dropTable({ tableName });
1763
- }
1764
- async alterTable({
1765
- tableName,
1766
- schema,
1767
- ifNotExists
1768
- }) {
1769
- return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
1770
- }
1771
- async clearTable({ tableName }) {
1772
- return this.stores.operations.clearTable({ tableName });
1773
- }
1774
- async insert({ tableName, record }) {
1775
- return this.stores.operations.insert({ tableName, record });
1776
- }
1777
- async batchInsert({ tableName, records }) {
1778
- return this.stores.operations.batchInsert({ tableName, records });
1779
- }
1780
- async load({ tableName, keys }) {
1781
- return this.stores.operations.load({ tableName, keys });
1782
- }
1783
- async getThreadById({ threadId }) {
1784
- return this.stores.memory.getThreadById({ threadId });
1901
+ return instance;
1902
+ } catch (e) {
1903
+ throw new MastraError(
1904
+ {
1905
+ id: createStorageErrorId("LANCE", "CONNECT", "FAILED"),
1906
+ domain: ErrorDomain.STORAGE,
1907
+ category: ErrorCategory.THIRD_PARTY,
1908
+ text: `Failed to connect to LanceDB: ${e.message || e}`,
1909
+ details: { uri, optionsProvided: !!connectionOptions }
1910
+ },
1911
+ e
1912
+ );
1913
+ }
1785
1914
  }
1786
1915
  /**
1787
- * Saves a thread to the database. This function doesn't overwrite existing threads.
1788
- * @param thread - The thread to save
1789
- * @returns The saved thread
1916
+ * Creates a new instance of LanceStorage from a pre-configured LanceDB connection.
1917
+ * Use this when you need to configure the connection before initialization.
1918
+ *
1919
+ * @param id The unique identifier for this storage instance
1920
+ * @param name The name for this storage instance
1921
+ * @param client Pre-configured LanceDB connection
1922
+ * @param options Storage options including disableInit
1923
+ *
1924
+ * @example
1925
+ * ```typescript
1926
+ * import { connect } from '@lancedb/lancedb';
1927
+ *
1928
+ * const client = await connect('/path/to/db', {
1929
+ * // Custom connection options
1930
+ * });
1931
+ *
1932
+ * const store = LanceStorage.fromClient('my-id', 'MyStorage', client);
1933
+ * ```
1790
1934
  */
1791
- async saveThread({ thread }) {
1792
- return this.stores.memory.saveThread({ thread });
1793
- }
1794
- async updateThread({
1795
- id,
1796
- title,
1797
- metadata
1798
- }) {
1799
- return this.stores.memory.updateThread({ id, title, metadata });
1800
- }
1801
- async deleteThread({ threadId }) {
1802
- return this.stores.memory.deleteThread({ threadId });
1803
- }
1804
- get supports() {
1805
- return {
1806
- selectByIncludeResourceScope: true,
1807
- resourceWorkingMemory: true,
1808
- hasColumn: true,
1809
- createTable: true,
1810
- deleteMessages: false,
1811
- listScoresBySpan: true
1935
+ static fromClient(id, name, client, options) {
1936
+ const instance = new _LanceStorage(id, name, options?.disableInit);
1937
+ instance.lanceClient = client;
1938
+ instance.stores = {
1939
+ workflows: new StoreWorkflowsLance({ client }),
1940
+ scores: new StoreScoresLance({ client }),
1941
+ memory: new StoreMemoryLance({ client })
1812
1942
  };
1813
- }
1814
- async getResourceById({ resourceId }) {
1815
- return this.stores.memory.getResourceById({ resourceId });
1816
- }
1817
- async saveResource({ resource }) {
1818
- return this.stores.memory.saveResource({ resource });
1819
- }
1820
- async updateResource({
1821
- resourceId,
1822
- workingMemory,
1823
- metadata
1824
- }) {
1825
- return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
1943
+ return instance;
1826
1944
  }
1827
1945
  /**
1828
- * Processes messages to include context messages based on withPreviousMessages and withNextMessages
1829
- * @param records - The sorted array of records to process
1830
- * @param include - The array of include specifications with context parameters
1831
- * @returns The processed array with context messages included
1946
+ * @internal
1947
+ * Private constructor to enforce using the create factory method.
1948
+ * Note: stores is initialized in create() after the lanceClient is connected.
1832
1949
  */
1833
- processMessagesWithContext(records, include) {
1834
- const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
1835
- if (messagesWithContext.length === 0) {
1836
- return records;
1837
- }
1838
- const messageIndexMap = /* @__PURE__ */ new Map();
1839
- records.forEach((message, index) => {
1840
- messageIndexMap.set(message.id, index);
1841
- });
1842
- const additionalIndices = /* @__PURE__ */ new Set();
1843
- for (const item of messagesWithContext) {
1844
- const messageIndex = messageIndexMap.get(item.id);
1845
- if (messageIndex !== void 0) {
1846
- if (item.withPreviousMessages) {
1847
- const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
1848
- for (let i = startIdx; i < messageIndex; i++) {
1849
- additionalIndices.add(i);
1850
- }
1851
- }
1852
- if (item.withNextMessages) {
1853
- const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
1854
- for (let i = messageIndex + 1; i <= endIdx; i++) {
1855
- additionalIndices.add(i);
1856
- }
1857
- }
1858
- }
1859
- }
1860
- if (additionalIndices.size === 0) {
1861
- return records;
1862
- }
1863
- const originalMatchIds = new Set(include.map((item) => item.id));
1864
- const allIndices = /* @__PURE__ */ new Set();
1865
- records.forEach((record, index) => {
1866
- if (originalMatchIds.has(record.id)) {
1867
- allIndices.add(index);
1868
- }
1869
- });
1870
- additionalIndices.forEach((index) => {
1871
- allIndices.add(index);
1872
- });
1873
- return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
1874
- }
1875
- async listMessagesById({ messageIds }) {
1876
- return this.stores.memory.listMessagesById({ messageIds });
1877
- }
1878
- async saveMessages(args) {
1879
- return this.stores.memory.saveMessages(args);
1880
- }
1881
- async updateMessages(_args) {
1882
- return this.stores.memory.updateMessages(_args);
1883
- }
1884
- async listWorkflowRuns(args) {
1885
- return this.stores.workflows.listWorkflowRuns(args);
1886
- }
1887
- async getWorkflowRunById(args) {
1888
- return this.stores.workflows.getWorkflowRunById(args);
1889
- }
1890
- async updateWorkflowResults({
1891
- workflowName,
1892
- runId,
1893
- stepId,
1894
- result,
1895
- requestContext
1896
- }) {
1897
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
1898
- }
1899
- async updateWorkflowState({
1900
- workflowName,
1901
- runId,
1902
- opts
1903
- }) {
1904
- return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
1905
- }
1906
- async persistWorkflowSnapshot({
1907
- workflowName,
1908
- runId,
1909
- resourceId,
1910
- snapshot
1911
- }) {
1912
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
1913
- }
1914
- async loadWorkflowSnapshot({
1915
- workflowName,
1916
- runId
1917
- }) {
1918
- return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
1919
- }
1920
- async getScoreById({ id: _id }) {
1921
- return this.stores.scores.getScoreById({ id: _id });
1922
- }
1923
- async listScoresByScorerId({
1924
- scorerId,
1925
- source,
1926
- entityId,
1927
- entityType,
1928
- pagination
1929
- }) {
1930
- return this.stores.scores.listScoresByScorerId({ scorerId, source, pagination, entityId, entityType });
1931
- }
1932
- async saveScore(_score) {
1933
- return this.stores.scores.saveScore(_score);
1934
- }
1935
- async listScoresByRunId({
1936
- runId,
1937
- pagination
1938
- }) {
1939
- return this.stores.scores.listScoresByRunId({ runId, pagination });
1940
- }
1941
- async listScoresByEntityId({
1942
- entityId,
1943
- entityType,
1944
- pagination
1945
- }) {
1946
- return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
1947
- }
1948
- async listScoresBySpan({
1949
- traceId,
1950
- spanId,
1951
- pagination
1952
- }) {
1953
- return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
1950
+ constructor(id, name, disableInit) {
1951
+ super({ id, name, disableInit });
1952
+ this.stores = {};
1954
1953
  }
1955
1954
  };
1956
1955
  var LanceFilterTranslator = class extends BaseFilterTranslator {
@@ -2306,7 +2305,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2306
2305
  } catch (e) {
2307
2306
  throw new MastraError(
2308
2307
  {
2309
- id: "STORAGE_LANCE_VECTOR_CONNECT_FAILED",
2308
+ id: createVectorErrorId("LANCE", "CONNECT", "FAILED"),
2310
2309
  domain: ErrorDomain.STORAGE,
2311
2310
  category: ErrorCategory.THIRD_PARTY,
2312
2311
  details: { uri }
@@ -2349,7 +2348,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2349
2348
  } catch (error) {
2350
2349
  throw new MastraError(
2351
2350
  {
2352
- id: "STORAGE_LANCE_VECTOR_QUERY_FAILED_INVALID_ARGS",
2351
+ id: createVectorErrorId("LANCE", "QUERY", "INVALID_ARGS"),
2353
2352
  domain: ErrorDomain.STORAGE,
2354
2353
  category: ErrorCategory.USER,
2355
2354
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2397,7 +2396,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2397
2396
  } catch (error) {
2398
2397
  throw new MastraError(
2399
2398
  {
2400
- id: "STORAGE_LANCE_VECTOR_QUERY_FAILED",
2399
+ id: createVectorErrorId("LANCE", "QUERY", "FAILED"),
2401
2400
  domain: ErrorDomain.STORAGE,
2402
2401
  category: ErrorCategory.THIRD_PARTY,
2403
2402
  details: { tableName, includeVector, columnsCount: columns?.length, includeAllColumns }
@@ -2449,7 +2448,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2449
2448
  } catch (error) {
2450
2449
  throw new MastraError(
2451
2450
  {
2452
- id: "STORAGE_LANCE_VECTOR_UPSERT_FAILED_INVALID_ARGS",
2451
+ id: createVectorErrorId("LANCE", "UPSERT", "INVALID_ARGS"),
2453
2452
  domain: ErrorDomain.STORAGE,
2454
2453
  category: ErrorCategory.USER,
2455
2454
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2485,7 +2484,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2485
2484
  } catch (error) {
2486
2485
  throw new MastraError(
2487
2486
  {
2488
- id: "STORAGE_LANCE_VECTOR_UPSERT_FAILED",
2487
+ id: createVectorErrorId("LANCE", "UPSERT", "FAILED"),
2489
2488
  domain: ErrorDomain.STORAGE,
2490
2489
  category: ErrorCategory.THIRD_PARTY,
2491
2490
  details: { tableName, vectorCount: vectors.length, metadataCount: metadata.length, idsCount: ids.length }
@@ -2512,7 +2511,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2512
2511
  async createTable(tableName, data, options) {
2513
2512
  if (!this.lanceClient) {
2514
2513
  throw new MastraError({
2515
- id: "STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED_INVALID_ARGS",
2514
+ id: createVectorErrorId("LANCE", "CREATE_TABLE", "INVALID_ARGS"),
2516
2515
  domain: ErrorDomain.STORAGE,
2517
2516
  category: ErrorCategory.USER,
2518
2517
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2527,7 +2526,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2527
2526
  } catch (error) {
2528
2527
  throw new MastraError(
2529
2528
  {
2530
- id: "STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED",
2529
+ id: createVectorErrorId("LANCE", "CREATE_TABLE", "FAILED"),
2531
2530
  domain: ErrorDomain.STORAGE,
2532
2531
  category: ErrorCategory.THIRD_PARTY,
2533
2532
  details: { tableName }
@@ -2539,7 +2538,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2539
2538
  async listTables() {
2540
2539
  if (!this.lanceClient) {
2541
2540
  throw new MastraError({
2542
- id: "STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED_INVALID_ARGS",
2541
+ id: createVectorErrorId("LANCE", "LIST_TABLES", "INVALID_ARGS"),
2543
2542
  domain: ErrorDomain.STORAGE,
2544
2543
  category: ErrorCategory.USER,
2545
2544
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2551,7 +2550,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2551
2550
  } catch (error) {
2552
2551
  throw new MastraError(
2553
2552
  {
2554
- id: "STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED",
2553
+ id: createVectorErrorId("LANCE", "LIST_TABLES", "FAILED"),
2555
2554
  domain: ErrorDomain.STORAGE,
2556
2555
  category: ErrorCategory.THIRD_PARTY
2557
2556
  },
@@ -2562,7 +2561,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2562
2561
  async getTableSchema(tableName) {
2563
2562
  if (!this.lanceClient) {
2564
2563
  throw new MastraError({
2565
- id: "STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED_INVALID_ARGS",
2564
+ id: createVectorErrorId("LANCE", "GET_TABLE_SCHEMA", "INVALID_ARGS"),
2566
2565
  domain: ErrorDomain.STORAGE,
2567
2566
  category: ErrorCategory.USER,
2568
2567
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2575,7 +2574,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2575
2574
  } catch (error) {
2576
2575
  throw new MastraError(
2577
2576
  {
2578
- id: "STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED",
2577
+ id: createVectorErrorId("LANCE", "GET_TABLE_SCHEMA", "FAILED"),
2579
2578
  domain: ErrorDomain.STORAGE,
2580
2579
  category: ErrorCategory.THIRD_PARTY,
2581
2580
  details: { tableName }
@@ -2610,7 +2609,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2610
2609
  } catch (err) {
2611
2610
  throw new MastraError(
2612
2611
  {
2613
- id: "STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED_INVALID_ARGS",
2612
+ id: createVectorErrorId("LANCE", "CREATE_INDEX", "INVALID_ARGS"),
2614
2613
  domain: ErrorDomain.STORAGE,
2615
2614
  category: ErrorCategory.USER,
2616
2615
  details: { tableName: tableName || "", indexName, dimension, metric }
@@ -2655,7 +2654,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2655
2654
  } catch (error) {
2656
2655
  throw new MastraError(
2657
2656
  {
2658
- id: "STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED",
2657
+ id: createVectorErrorId("LANCE", "CREATE_INDEX", "FAILED"),
2659
2658
  domain: ErrorDomain.STORAGE,
2660
2659
  category: ErrorCategory.THIRD_PARTY,
2661
2660
  details: { tableName: tableName || "", indexName, dimension }
@@ -2667,7 +2666,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2667
2666
  async listIndexes() {
2668
2667
  if (!this.lanceClient) {
2669
2668
  throw new MastraError({
2670
- id: "STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED_INVALID_ARGS",
2669
+ id: createVectorErrorId("LANCE", "LIST_INDEXES", "INVALID_ARGS"),
2671
2670
  domain: ErrorDomain.STORAGE,
2672
2671
  category: ErrorCategory.USER,
2673
2672
  text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
@@ -2686,7 +2685,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2686
2685
  } catch (error) {
2687
2686
  throw new MastraError(
2688
2687
  {
2689
- id: "STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED",
2688
+ id: createVectorErrorId("LANCE", "LIST_INDEXES", "FAILED"),
2690
2689
  domain: ErrorDomain.STORAGE,
2691
2690
  category: ErrorCategory.THIRD_PARTY
2692
2691
  },
@@ -2705,7 +2704,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2705
2704
  } catch (err) {
2706
2705
  throw new MastraError(
2707
2706
  {
2708
- id: "STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED_INVALID_ARGS",
2707
+ id: createVectorErrorId("LANCE", "DESCRIBE_INDEX", "INVALID_ARGS"),
2709
2708
  domain: ErrorDomain.STORAGE,
2710
2709
  category: ErrorCategory.USER,
2711
2710
  details: { indexName }
@@ -2740,7 +2739,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2740
2739
  } catch (error) {
2741
2740
  throw new MastraError(
2742
2741
  {
2743
- id: "STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED",
2742
+ id: createVectorErrorId("LANCE", "DESCRIBE_INDEX", "FAILED"),
2744
2743
  domain: ErrorDomain.STORAGE,
2745
2744
  category: ErrorCategory.THIRD_PARTY,
2746
2745
  details: { indexName }
@@ -2760,7 +2759,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2760
2759
  } catch (err) {
2761
2760
  throw new MastraError(
2762
2761
  {
2763
- id: "STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED_INVALID_ARGS",
2762
+ id: createVectorErrorId("LANCE", "DELETE_INDEX", "INVALID_ARGS"),
2764
2763
  domain: ErrorDomain.STORAGE,
2765
2764
  category: ErrorCategory.USER,
2766
2765
  details: { indexName }
@@ -2783,7 +2782,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2783
2782
  } catch (error) {
2784
2783
  throw new MastraError(
2785
2784
  {
2786
- id: "STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED",
2785
+ id: createVectorErrorId("LANCE", "DELETE_INDEX", "FAILED"),
2787
2786
  domain: ErrorDomain.STORAGE,
2788
2787
  category: ErrorCategory.THIRD_PARTY,
2789
2788
  details: { indexName }
@@ -2798,7 +2797,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2798
2797
  async deleteAllTables() {
2799
2798
  if (!this.lanceClient) {
2800
2799
  throw new MastraError({
2801
- id: "STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED_INVALID_ARGS",
2800
+ id: createVectorErrorId("LANCE", "DELETE_ALL_TABLES", "INVALID_ARGS"),
2802
2801
  domain: ErrorDomain.STORAGE,
2803
2802
  category: ErrorCategory.USER,
2804
2803
  details: { methodName: "deleteAllTables" },
@@ -2810,7 +2809,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2810
2809
  } catch (error) {
2811
2810
  throw new MastraError(
2812
2811
  {
2813
- id: "STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED",
2812
+ id: createVectorErrorId("LANCE", "DELETE_ALL_TABLES", "FAILED"),
2814
2813
  domain: ErrorDomain.STORAGE,
2815
2814
  category: ErrorCategory.THIRD_PARTY,
2816
2815
  details: { methodName: "deleteAllTables" }
@@ -2822,7 +2821,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2822
2821
  async deleteTable(tableName) {
2823
2822
  if (!this.lanceClient) {
2824
2823
  throw new MastraError({
2825
- id: "STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED_INVALID_ARGS",
2824
+ id: createVectorErrorId("LANCE", "DELETE_TABLE", "INVALID_ARGS"),
2826
2825
  domain: ErrorDomain.STORAGE,
2827
2826
  category: ErrorCategory.USER,
2828
2827
  details: { tableName },
@@ -2834,7 +2833,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2834
2833
  } catch (error) {
2835
2834
  throw new MastraError(
2836
2835
  {
2837
- id: "STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED",
2836
+ id: createVectorErrorId("LANCE", "DELETE_TABLE", "FAILED"),
2838
2837
  domain: ErrorDomain.STORAGE,
2839
2838
  category: ErrorCategory.THIRD_PARTY,
2840
2839
  details: { tableName }
@@ -2843,7 +2842,44 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2843
2842
  );
2844
2843
  }
2845
2844
  }
2846
- async updateVector({ indexName, id, update }) {
2845
+ async updateVector(params) {
2846
+ const { indexName, update } = params;
2847
+ if ("id" in params && "filter" in params && params.id && params.filter) {
2848
+ throw new MastraError({
2849
+ id: createVectorErrorId("LANCE", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
2850
+ domain: ErrorDomain.STORAGE,
2851
+ category: ErrorCategory.USER,
2852
+ text: "id and filter are mutually exclusive",
2853
+ details: { indexName }
2854
+ });
2855
+ }
2856
+ if (!("id" in params || "filter" in params) || !params.id && !params.filter) {
2857
+ throw new MastraError({
2858
+ id: createVectorErrorId("LANCE", "UPDATE_VECTOR", "NO_TARGET"),
2859
+ domain: ErrorDomain.STORAGE,
2860
+ category: ErrorCategory.USER,
2861
+ text: "Either id or filter must be provided",
2862
+ details: { indexName }
2863
+ });
2864
+ }
2865
+ if ("filter" in params && params.filter && Object.keys(params.filter).length === 0) {
2866
+ throw new MastraError({
2867
+ id: createVectorErrorId("LANCE", "UPDATE_VECTOR", "EMPTY_FILTER"),
2868
+ domain: ErrorDomain.STORAGE,
2869
+ category: ErrorCategory.USER,
2870
+ text: "Cannot update with empty filter",
2871
+ details: { indexName }
2872
+ });
2873
+ }
2874
+ if (!update.vector && !update.metadata) {
2875
+ throw new MastraError({
2876
+ id: createVectorErrorId("LANCE", "UPDATE_VECTOR", "NO_PAYLOAD"),
2877
+ domain: ErrorDomain.STORAGE,
2878
+ category: ErrorCategory.USER,
2879
+ text: "No updates provided",
2880
+ details: { indexName }
2881
+ });
2882
+ }
2847
2883
  try {
2848
2884
  if (!this.lanceClient) {
2849
2885
  throw new Error("LanceDB client not initialized. Use LanceVectorStore.create() to create an instance");
@@ -2851,21 +2887,6 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2851
2887
  if (!indexName) {
2852
2888
  throw new Error("indexName is required");
2853
2889
  }
2854
- if (!id) {
2855
- throw new Error("id is required");
2856
- }
2857
- } catch (err) {
2858
- throw new MastraError(
2859
- {
2860
- id: "STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED_INVALID_ARGS",
2861
- domain: ErrorDomain.STORAGE,
2862
- category: ErrorCategory.USER,
2863
- details: { indexName, id }
2864
- },
2865
- err
2866
- );
2867
- }
2868
- try {
2869
2890
  const tables = await this.lanceClient.tableNames();
2870
2891
  for (const tableName of tables) {
2871
2892
  this.logger.debug("Checking table:" + tableName);
@@ -2875,39 +2896,66 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2875
2896
  const hasColumn = schema.fields.some((field) => field.name === indexName);
2876
2897
  if (hasColumn) {
2877
2898
  this.logger.debug(`Found column ${indexName} in table ${tableName}`);
2878
- const existingRecord = await table.query().where(`id = '${id}'`).select(schema.fields.map((field) => field.name)).limit(1).toArray();
2879
- if (existingRecord.length === 0) {
2880
- throw new Error(`Record with id '${id}' not found in table ${tableName}`);
2899
+ let whereClause;
2900
+ if ("id" in params && params.id) {
2901
+ whereClause = `id = '${params.id}'`;
2902
+ } else if ("filter" in params && params.filter) {
2903
+ const translator = new LanceFilterTranslator();
2904
+ const processFilterKeys = (filter) => {
2905
+ const processedFilter = {};
2906
+ Object.entries(filter).forEach(([key, value]) => {
2907
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2908
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
2909
+ processedFilter[`metadata_${key}_${nestedKey}`] = nestedValue;
2910
+ });
2911
+ } else {
2912
+ processedFilter[`metadata_${key}`] = value;
2913
+ }
2914
+ });
2915
+ return processedFilter;
2916
+ };
2917
+ const prefixedFilter = processFilterKeys(params.filter);
2918
+ whereClause = translator.translate(prefixedFilter) || "";
2919
+ if (!whereClause) {
2920
+ throw new Error("Failed to translate filter to SQL");
2921
+ }
2922
+ } else {
2923
+ throw new Error("Either id or filter must be provided");
2881
2924
  }
2882
- const rowData = {
2883
- id
2884
- };
2885
- Object.entries(existingRecord[0]).forEach(([key, value]) => {
2886
- if (key !== "id" && key !== "_distance") {
2887
- if (key === indexName) {
2888
- if (!update.vector) {
2889
- if (Array.isArray(value)) {
2890
- rowData[key] = [...value];
2891
- } else if (typeof value === "object" && value !== null) {
2892
- rowData[key] = Array.from(value);
2925
+ const existingRecords = await table.query().where(whereClause).select(schema.fields.map((field) => field.name)).toArray();
2926
+ if (existingRecords.length === 0) {
2927
+ this.logger.info(`No records found matching criteria in table ${tableName}`);
2928
+ return;
2929
+ }
2930
+ const updatedRecords = existingRecords.map((record) => {
2931
+ const rowData = {};
2932
+ Object.entries(record).forEach(([key, value]) => {
2933
+ if (key !== "_distance") {
2934
+ if (key === indexName) {
2935
+ if (update.vector) {
2936
+ rowData[key] = update.vector;
2893
2937
  } else {
2894
- rowData[key] = value;
2938
+ if (Array.isArray(value)) {
2939
+ rowData[key] = [...value];
2940
+ } else if (typeof value === "object" && value !== null) {
2941
+ rowData[key] = Array.from(value);
2942
+ } else {
2943
+ rowData[key] = value;
2944
+ }
2895
2945
  }
2946
+ } else {
2947
+ rowData[key] = value;
2896
2948
  }
2897
- } else {
2898
- rowData[key] = value;
2899
2949
  }
2950
+ });
2951
+ if (update.metadata) {
2952
+ Object.entries(update.metadata).forEach(([key, value]) => {
2953
+ rowData[`metadata_${key}`] = value;
2954
+ });
2900
2955
  }
2956
+ return rowData;
2901
2957
  });
2902
- if (update.vector) {
2903
- rowData[indexName] = update.vector;
2904
- }
2905
- if (update.metadata) {
2906
- Object.entries(update.metadata).forEach(([key, value]) => {
2907
- rowData[`metadata_${key}`] = value;
2908
- });
2909
- }
2910
- await table.add([rowData], { mode: "overwrite" });
2958
+ await table.add(updatedRecords, { mode: "overwrite" });
2911
2959
  return;
2912
2960
  }
2913
2961
  } catch (err) {
@@ -2917,12 +2965,19 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2917
2965
  }
2918
2966
  throw new Error(`No table found with column/index '${indexName}'`);
2919
2967
  } catch (error) {
2968
+ if (error instanceof MastraError) throw error;
2920
2969
  throw new MastraError(
2921
2970
  {
2922
- id: "STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED",
2971
+ id: createVectorErrorId("LANCE", "UPDATE_VECTOR", "FAILED"),
2923
2972
  domain: ErrorDomain.STORAGE,
2924
2973
  category: ErrorCategory.THIRD_PARTY,
2925
- details: { indexName, id, hasVector: !!update.vector, hasMetadata: !!update.metadata }
2974
+ details: {
2975
+ indexName,
2976
+ ..."id" in params && params.id && { id: params.id },
2977
+ ..."filter" in params && params.filter && { filter: JSON.stringify(params.filter) },
2978
+ hasVector: !!update.vector,
2979
+ hasMetadata: !!update.metadata
2980
+ }
2926
2981
  },
2927
2982
  error
2928
2983
  );
@@ -2942,10 +2997,13 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2942
2997
  } catch (err) {
2943
2998
  throw new MastraError(
2944
2999
  {
2945
- id: "STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED_INVALID_ARGS",
3000
+ id: createVectorErrorId("LANCE", "DELETE_VECTOR", "INVALID_ARGS"),
2946
3001
  domain: ErrorDomain.STORAGE,
2947
3002
  category: ErrorCategory.USER,
2948
- details: { indexName, id }
3003
+ details: {
3004
+ indexName,
3005
+ ...id && { id }
3006
+ }
2949
3007
  },
2950
3008
  err
2951
3009
  );
@@ -2972,10 +3030,13 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2972
3030
  } catch (error) {
2973
3031
  throw new MastraError(
2974
3032
  {
2975
- id: "STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED",
3033
+ id: createVectorErrorId("LANCE", "DELETE_VECTOR", "FAILED"),
2976
3034
  domain: ErrorDomain.STORAGE,
2977
3035
  category: ErrorCategory.THIRD_PARTY,
2978
- details: { indexName, id }
3036
+ details: {
3037
+ indexName,
3038
+ ...id && { id }
3039
+ }
2979
3040
  },
2980
3041
  error
2981
3042
  );
@@ -3006,8 +3067,111 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
3006
3067
  });
3007
3068
  return result;
3008
3069
  }
3070
+ async deleteVectors({ indexName, filter, ids }) {
3071
+ if (ids && filter) {
3072
+ throw new MastraError({
3073
+ id: createVectorErrorId("LANCE", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
3074
+ domain: ErrorDomain.STORAGE,
3075
+ category: ErrorCategory.USER,
3076
+ text: "ids and filter are mutually exclusive",
3077
+ details: { indexName }
3078
+ });
3079
+ }
3080
+ if (!ids && !filter) {
3081
+ throw new MastraError({
3082
+ id: createVectorErrorId("LANCE", "DELETE_VECTORS", "NO_TARGET"),
3083
+ domain: ErrorDomain.STORAGE,
3084
+ category: ErrorCategory.USER,
3085
+ text: "Either filter or ids must be provided",
3086
+ details: { indexName }
3087
+ });
3088
+ }
3089
+ if (ids && ids.length === 0) {
3090
+ throw new MastraError({
3091
+ id: createVectorErrorId("LANCE", "DELETE_VECTORS", "EMPTY_IDS"),
3092
+ domain: ErrorDomain.STORAGE,
3093
+ category: ErrorCategory.USER,
3094
+ text: "Cannot delete with empty ids array",
3095
+ details: { indexName }
3096
+ });
3097
+ }
3098
+ if (filter && Object.keys(filter).length === 0) {
3099
+ throw new MastraError({
3100
+ id: createVectorErrorId("LANCE", "DELETE_VECTORS", "EMPTY_FILTER"),
3101
+ domain: ErrorDomain.STORAGE,
3102
+ category: ErrorCategory.USER,
3103
+ text: "Cannot delete with empty filter",
3104
+ details: { indexName }
3105
+ });
3106
+ }
3107
+ try {
3108
+ if (!this.lanceClient) {
3109
+ throw new Error("LanceDB client not initialized. Use LanceVectorStore.create() to create an instance");
3110
+ }
3111
+ if (!indexName) {
3112
+ throw new Error("indexName is required");
3113
+ }
3114
+ const tables = await this.lanceClient.tableNames();
3115
+ for (const tableName of tables) {
3116
+ this.logger.debug("Checking table:" + tableName);
3117
+ const table = await this.lanceClient.openTable(tableName);
3118
+ try {
3119
+ const schema = await table.schema();
3120
+ const hasColumn = schema.fields.some((field) => field.name === indexName);
3121
+ if (hasColumn) {
3122
+ this.logger.debug(`Found column ${indexName} in table ${tableName}`);
3123
+ if (ids) {
3124
+ const idsConditions = ids.map((id) => `id = '${id}'`).join(" OR ");
3125
+ await table.delete(idsConditions);
3126
+ } else if (filter) {
3127
+ const translator = new LanceFilterTranslator();
3128
+ const processFilterKeys = (filter2) => {
3129
+ const processedFilter = {};
3130
+ Object.entries(filter2).forEach(([key, value]) => {
3131
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3132
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
3133
+ processedFilter[`metadata_${key}_${nestedKey}`] = nestedValue;
3134
+ });
3135
+ } else {
3136
+ processedFilter[`metadata_${key}`] = value;
3137
+ }
3138
+ });
3139
+ return processedFilter;
3140
+ };
3141
+ const prefixedFilter = processFilterKeys(filter);
3142
+ const whereClause = translator.translate(prefixedFilter);
3143
+ if (!whereClause) {
3144
+ throw new Error("Failed to translate filter to SQL");
3145
+ }
3146
+ await table.delete(whereClause);
3147
+ }
3148
+ return;
3149
+ }
3150
+ } catch (err) {
3151
+ this.logger.error(`Error checking schema for table ${tableName}:` + err);
3152
+ continue;
3153
+ }
3154
+ }
3155
+ throw new Error(`No table found with column/index '${indexName}'`);
3156
+ } catch (error) {
3157
+ if (error instanceof MastraError) throw error;
3158
+ throw new MastraError(
3159
+ {
3160
+ id: createVectorErrorId("LANCE", "DELETE_VECTORS", "FAILED"),
3161
+ domain: ErrorDomain.STORAGE,
3162
+ category: ErrorCategory.THIRD_PARTY,
3163
+ details: {
3164
+ indexName,
3165
+ ...filter && { filter: JSON.stringify(filter) },
3166
+ ...ids && { idsCount: ids.length }
3167
+ }
3168
+ },
3169
+ error
3170
+ );
3171
+ }
3172
+ }
3009
3173
  };
3010
3174
 
3011
- export { LanceStorage, LanceVectorStore };
3175
+ export { LanceStorage, LanceVectorStore, StoreMemoryLance, StoreScoresLance, StoreWorkflowsLance };
3012
3176
  //# sourceMappingURL=index.js.map
3013
3177
  //# sourceMappingURL=index.js.map