@mastra/lance 1.0.0-beta.6 → 1.0.0-beta.8

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.cjs CHANGED
@@ -4,6 +4,7 @@ var lancedb = require('@lancedb/lancedb');
4
4
  var error = require('@mastra/core/error');
5
5
  var storage = require('@mastra/core/storage');
6
6
  var agent = require('@mastra/core/agent');
7
+ var base = require('@mastra/core/base');
7
8
  var apacheArrow = require('apache-arrow');
8
9
  var evals = require('@mastra/core/evals');
9
10
  var vector = require('@mastra/core/vector');
@@ -124,1119 +125,1198 @@ async function getTableSchema({
124
125
  }
125
126
  }
126
127
 
127
- // src/storage/domains/memory/index.ts
128
- var StoreMemoryLance = class extends storage.MemoryStorage {
128
+ // src/storage/db/index.ts
129
+ function resolveLanceConfig(config) {
130
+ return config.client;
131
+ }
132
+ var LanceDB = class extends base.MastraBase {
129
133
  client;
130
- operations;
131
- constructor({ client, operations }) {
132
- super();
134
+ constructor({ client }) {
135
+ super({ name: "lance-db" });
133
136
  this.client = client;
134
- this.operations = operations;
135
137
  }
136
- // Utility to escape single quotes in SQL strings
137
- escapeSql(str) {
138
- return str.replace(/'/g, "''");
138
+ getDefaultValue(type) {
139
+ switch (type) {
140
+ case "text":
141
+ return "''";
142
+ case "timestamp":
143
+ return "CURRENT_TIMESTAMP";
144
+ case "integer":
145
+ case "bigint":
146
+ return "0";
147
+ case "jsonb":
148
+ return "'{}'";
149
+ case "uuid":
150
+ return "''";
151
+ default:
152
+ return storage.getDefaultValue(type);
153
+ }
139
154
  }
140
- async getThreadById({ threadId }) {
155
+ async hasColumn(tableName, columnName) {
156
+ const table = await this.client.openTable(tableName);
157
+ const schema = await table.schema();
158
+ return schema.fields.some((field) => field.name === columnName);
159
+ }
160
+ translateSchema(schema) {
161
+ const fields = Object.entries(schema).map(([name, column]) => {
162
+ let arrowType;
163
+ switch (column.type.toLowerCase()) {
164
+ case "text":
165
+ case "uuid":
166
+ arrowType = new apacheArrow.Utf8();
167
+ break;
168
+ case "int":
169
+ case "integer":
170
+ arrowType = new apacheArrow.Int32();
171
+ break;
172
+ case "bigint":
173
+ arrowType = new apacheArrow.Float64();
174
+ break;
175
+ case "float":
176
+ arrowType = new apacheArrow.Float32();
177
+ break;
178
+ case "jsonb":
179
+ case "json":
180
+ arrowType = new apacheArrow.Utf8();
181
+ break;
182
+ case "binary":
183
+ arrowType = new apacheArrow.Binary();
184
+ break;
185
+ case "timestamp":
186
+ arrowType = new apacheArrow.Float64();
187
+ break;
188
+ default:
189
+ arrowType = new apacheArrow.Utf8();
190
+ }
191
+ return new apacheArrow.Field(name, arrowType, column.nullable ?? true);
192
+ });
193
+ return new apacheArrow.Schema(fields);
194
+ }
195
+ async createTable({
196
+ tableName,
197
+ schema
198
+ }) {
141
199
  try {
142
- const thread = await this.operations.load({ tableName: storage.TABLE_THREADS, keys: { id: threadId } });
143
- if (!thread) {
144
- return null;
200
+ if (!this.client) {
201
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
202
+ }
203
+ if (!tableName) {
204
+ throw new Error("tableName is required for createTable.");
205
+ }
206
+ if (!schema) {
207
+ throw new Error("schema is required for createTable.");
145
208
  }
146
- return {
147
- ...thread,
148
- createdAt: new Date(thread.createdAt),
149
- updatedAt: new Date(thread.updatedAt)
150
- };
151
209
  } catch (error$1) {
152
210
  throw new error.MastraError(
153
211
  {
154
- id: storage.createStorageErrorId("LANCE", "GET_THREAD_BY_ID", "FAILED"),
212
+ id: storage.createStorageErrorId("LANCE", "CREATE_TABLE", "INVALID_ARGS"),
155
213
  domain: error.ErrorDomain.STORAGE,
156
- category: error.ErrorCategory.THIRD_PARTY
214
+ category: error.ErrorCategory.USER,
215
+ details: { tableName }
157
216
  },
158
217
  error$1
159
218
  );
160
219
  }
161
- }
162
- /**
163
- * Saves a thread to the database. This function doesn't overwrite existing threads.
164
- * @param thread - The thread to save
165
- * @returns The saved thread
166
- */
167
- async saveThread({ thread }) {
168
220
  try {
169
- const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
170
- const table = await this.client.openTable(storage.TABLE_THREADS);
171
- await table.add([record], { mode: "append" });
172
- return thread;
221
+ const arrowSchema = this.translateSchema(schema);
222
+ await this.client.createEmptyTable(tableName, arrowSchema);
173
223
  } catch (error$1) {
224
+ if (error$1.message?.includes("already exists")) {
225
+ this.logger.debug(`Table '${tableName}' already exists, skipping create`);
226
+ return;
227
+ }
174
228
  throw new error.MastraError(
175
229
  {
176
- id: storage.createStorageErrorId("LANCE", "SAVE_THREAD", "FAILED"),
230
+ id: storage.createStorageErrorId("LANCE", "CREATE_TABLE", "FAILED"),
177
231
  domain: error.ErrorDomain.STORAGE,
178
- category: error.ErrorCategory.THIRD_PARTY
232
+ category: error.ErrorCategory.THIRD_PARTY,
233
+ details: { tableName }
179
234
  },
180
235
  error$1
181
236
  );
182
237
  }
183
238
  }
184
- async updateThread({
185
- id,
186
- title,
187
- metadata
188
- }) {
189
- const maxRetries = 5;
190
- for (let attempt = 0; attempt < maxRetries; attempt++) {
191
- try {
192
- const current = await this.getThreadById({ threadId: id });
193
- if (!current) {
194
- throw new Error(`Thread with id ${id} not found`);
195
- }
196
- const mergedMetadata = { ...current.metadata, ...metadata };
197
- const record = {
198
- id,
199
- title,
200
- metadata: JSON.stringify(mergedMetadata),
201
- updatedAt: (/* @__PURE__ */ new Date()).getTime()
202
- };
203
- const table = await this.client.openTable(storage.TABLE_THREADS);
204
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
205
- const updatedThread = await this.getThreadById({ threadId: id });
206
- if (!updatedThread) {
207
- throw new Error(`Failed to retrieve updated thread ${id}`);
208
- }
209
- return updatedThread;
210
- } catch (error$1) {
211
- if (error$1.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
212
- const delay = Math.pow(2, attempt) * 10;
213
- await new Promise((resolve) => setTimeout(resolve, delay));
214
- continue;
215
- }
216
- throw new error.MastraError(
217
- {
218
- id: storage.createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
219
- domain: error.ErrorDomain.STORAGE,
220
- category: error.ErrorCategory.THIRD_PARTY
221
- },
222
- error$1
223
- );
224
- }
225
- }
226
- throw new error.MastraError(
227
- {
228
- id: storage.createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
229
- domain: error.ErrorDomain.STORAGE,
230
- category: error.ErrorCategory.THIRD_PARTY
231
- },
232
- new Error("All retries exhausted")
233
- );
234
- }
235
- async deleteThread({ threadId }) {
239
+ async dropTable({ tableName }) {
236
240
  try {
237
- const table = await this.client.openTable(storage.TABLE_THREADS);
238
- await table.delete(`id = '${threadId}'`);
239
- const messagesTable = await this.client.openTable(storage.TABLE_MESSAGES);
240
- await messagesTable.delete(`thread_id = '${threadId}'`);
241
- } catch (error$1) {
241
+ if (!this.client) {
242
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
243
+ }
244
+ if (!tableName) {
245
+ throw new Error("tableName is required for dropTable.");
246
+ }
247
+ } catch (validationError) {
242
248
  throw new error.MastraError(
243
249
  {
244
- id: storage.createStorageErrorId("LANCE", "DELETE_THREAD", "FAILED"),
250
+ id: storage.createStorageErrorId("LANCE", "DROP_TABLE", "INVALID_ARGS"),
245
251
  domain: error.ErrorDomain.STORAGE,
246
- category: error.ErrorCategory.THIRD_PARTY
252
+ category: error.ErrorCategory.USER,
253
+ text: validationError.message,
254
+ details: { tableName }
247
255
  },
248
- error$1
256
+ validationError
249
257
  );
250
258
  }
251
- }
252
- normalizeMessage(message) {
253
- const { thread_id, ...rest } = message;
254
- return {
255
- ...rest,
256
- threadId: thread_id,
257
- content: typeof message.content === "string" ? (() => {
258
- try {
259
- return JSON.parse(message.content);
260
- } catch {
261
- return message.content;
262
- }
263
- })() : message.content
264
- };
265
- }
266
- async listMessagesById({ messageIds }) {
267
- if (messageIds.length === 0) return { messages: [] };
268
259
  try {
269
- const table = await this.client.openTable(storage.TABLE_MESSAGES);
270
- const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
271
- const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
272
- const messages = processResultWithTypeConversion(
273
- allRecords,
274
- await getTableSchema({ tableName: storage.TABLE_MESSAGES, client: this.client })
275
- );
276
- const list = new agent.MessageList().add(
277
- messages.map(this.normalizeMessage),
278
- "memory"
279
- );
280
- return { messages: list.get.all.db() };
260
+ await this.client.dropTable(tableName);
281
261
  } catch (error$1) {
262
+ if (error$1.toString().includes("was not found") || error$1.message?.includes("Table not found")) {
263
+ this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
264
+ return;
265
+ }
282
266
  throw new error.MastraError(
283
267
  {
284
- id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES_BY_ID", "FAILED"),
268
+ id: storage.createStorageErrorId("LANCE", "DROP_TABLE", "FAILED"),
285
269
  domain: error.ErrorDomain.STORAGE,
286
270
  category: error.ErrorCategory.THIRD_PARTY,
287
- details: {
288
- messageIds: JSON.stringify(messageIds)
289
- }
271
+ details: { tableName }
290
272
  },
291
273
  error$1
292
274
  );
293
275
  }
294
276
  }
295
- async listMessages(args) {
296
- const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
297
- const threadIds = Array.isArray(threadId) ? threadId : [threadId];
298
- if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
277
+ async alterTable({
278
+ tableName,
279
+ schema,
280
+ ifNotExists
281
+ }) {
282
+ try {
283
+ if (!this.client) {
284
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
285
+ }
286
+ if (!tableName) {
287
+ throw new Error("tableName is required for alterTable.");
288
+ }
289
+ if (!schema) {
290
+ throw new Error("schema is required for alterTable.");
291
+ }
292
+ if (!ifNotExists || ifNotExists.length === 0) {
293
+ this.logger.debug("No columns specified to add in alterTable, skipping.");
294
+ return;
295
+ }
296
+ } catch (validationError) {
299
297
  throw new error.MastraError(
300
298
  {
301
- id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_THREAD_ID"),
299
+ id: storage.createStorageErrorId("LANCE", "ALTER_TABLE", "INVALID_ARGS"),
302
300
  domain: error.ErrorDomain.STORAGE,
303
- category: error.ErrorCategory.THIRD_PARTY,
304
- details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
301
+ category: error.ErrorCategory.USER,
302
+ text: validationError.message,
303
+ details: { tableName }
305
304
  },
306
- new Error("threadId must be a non-empty string or array of non-empty strings")
305
+ validationError
307
306
  );
308
307
  }
309
- const perPage = storage.normalizePerPage(perPageInput, 40);
310
- const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
311
308
  try {
312
- if (page < 0) {
313
- throw new error.MastraError(
314
- {
315
- id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_PAGE"),
316
- domain: error.ErrorDomain.STORAGE,
317
- category: error.ErrorCategory.USER,
318
- details: { page }
319
- },
320
- new Error("page must be >= 0")
321
- );
322
- }
323
- const { field, direction } = this.parseOrderBy(orderBy, "ASC");
324
- const table = await this.client.openTable(storage.TABLE_MESSAGES);
325
- const threadCondition = threadIds.length === 1 ? `thread_id = '${this.escapeSql(threadIds[0])}'` : `thread_id IN (${threadIds.map((t) => `'${this.escapeSql(t)}'`).join(", ")})`;
326
- const conditions = [threadCondition];
327
- if (resourceId) {
328
- conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
329
- }
330
- if (filter?.dateRange?.start) {
331
- const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
332
- conditions.push(`\`createdAt\` >= ${startTime}`);
333
- }
334
- if (filter?.dateRange?.end) {
335
- const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
336
- conditions.push(`\`createdAt\` <= ${endTime}`);
337
- }
338
- const whereClause = conditions.join(" AND ");
339
- const total = await table.countRows(whereClause);
340
- const query = table.query().where(whereClause);
341
- let allRecords = await query.toArray();
342
- allRecords.sort((a, b) => {
343
- const aValue = field === "createdAt" ? a.createdAt : a[field];
344
- const bValue = field === "createdAt" ? b.createdAt : b[field];
345
- if (aValue == null && bValue == null) return 0;
346
- if (aValue == null) return direction === "ASC" ? -1 : 1;
347
- if (bValue == null) return direction === "ASC" ? 1 : -1;
348
- if (typeof aValue === "string" && typeof bValue === "string") {
349
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
350
- }
351
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
352
- });
353
- const paginatedRecords = allRecords.slice(offset, offset + perPage);
354
- const messages = paginatedRecords.map((row) => this.normalizeMessage(row));
355
- if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
309
+ const table = await this.client.openTable(tableName);
310
+ const currentSchema = await table.schema();
311
+ const existingFields = new Set(currentSchema.fields.map((f) => f.name));
312
+ const typeMap = {
313
+ text: "string",
314
+ integer: "int",
315
+ bigint: "bigint",
316
+ timestamp: "timestamp",
317
+ jsonb: "string",
318
+ uuid: "string"
319
+ };
320
+ const columnsToAdd = ifNotExists.filter((col) => schema[col] && !existingFields.has(col)).map((col) => {
321
+ const colDef = schema[col];
356
322
  return {
357
- messages: [],
358
- total: 0,
359
- page,
360
- perPage: perPageForResponse,
361
- hasMore: false
323
+ name: col,
324
+ valueSql: colDef?.nullable ? `cast(NULL as ${typeMap[colDef.type ?? "text"]})` : `cast(${this.getDefaultValue(colDef?.type ?? "text")} as ${typeMap[colDef?.type ?? "text"]})`
362
325
  };
363
- }
364
- const messageIds = new Set(messages.map((m) => m.id));
365
- if (include && include.length > 0) {
366
- const threadIds2 = [...new Set(include.map((item) => item.threadId || threadId))];
367
- const allThreadMessages = [];
368
- for (const tid of threadIds2) {
369
- const threadQuery = table.query().where(`thread_id = '${tid}'`);
370
- let threadRecords = await threadQuery.toArray();
371
- allThreadMessages.push(...threadRecords);
372
- }
373
- allThreadMessages.sort((a, b) => a.createdAt - b.createdAt);
374
- const contextMessages = this.processMessagesWithContext(allThreadMessages, include);
375
- const includedMessages = contextMessages.map((row) => this.normalizeMessage(row));
376
- for (const includeMsg of includedMessages) {
377
- if (!messageIds.has(includeMsg.id)) {
378
- messages.push(includeMsg);
379
- messageIds.add(includeMsg.id);
380
- }
381
- }
382
- }
383
- const list = new agent.MessageList().add(messages, "memory");
384
- let finalMessages = list.get.all.db();
385
- finalMessages = finalMessages.sort((a, b) => {
386
- const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
387
- const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
388
- if (aValue == null && bValue == null) return 0;
389
- if (aValue == null) return direction === "ASC" ? -1 : 1;
390
- if (bValue == null) return direction === "ASC" ? 1 : -1;
391
- if (typeof aValue === "string" && typeof bValue === "string") {
392
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
393
- }
394
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
395
326
  });
396
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
397
- const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
398
- const fetchedAll = perPageInput === false || allThreadMessagesReturned;
399
- const hasMore = !fetchedAll && offset + perPage < total;
400
- return {
401
- messages: finalMessages,
402
- total,
403
- page,
404
- perPage: perPageForResponse,
405
- hasMore
406
- };
327
+ if (columnsToAdd.length > 0) {
328
+ await table.addColumns(columnsToAdd);
329
+ this.logger?.info?.(`Added columns [${columnsToAdd.map((c) => c.name).join(", ")}] to table ${tableName}`);
330
+ }
407
331
  } catch (error$1) {
408
- const mastraError = new error.MastraError(
332
+ throw new error.MastraError(
409
333
  {
410
- id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "FAILED"),
334
+ id: storage.createStorageErrorId("LANCE", "ALTER_TABLE", "FAILED"),
411
335
  domain: error.ErrorDomain.STORAGE,
412
336
  category: error.ErrorCategory.THIRD_PARTY,
413
- details: {
414
- threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
415
- resourceId: resourceId ?? ""
416
- }
337
+ details: { tableName }
417
338
  },
418
339
  error$1
419
340
  );
420
- this.logger?.error?.(mastraError.toString());
421
- this.logger?.trackException?.(mastraError);
422
- return {
423
- messages: [],
424
- total: 0,
425
- page,
426
- perPage: perPageForResponse,
427
- hasMore: false
428
- };
429
341
  }
430
342
  }
431
- async saveMessages(args) {
343
+ async clearTable({ tableName }) {
432
344
  try {
433
- const { messages } = args;
434
- if (messages.length === 0) {
435
- return { messages: [] };
436
- }
437
- const threadId = messages[0]?.threadId;
438
- if (!threadId) {
439
- throw new Error("Thread ID is required");
345
+ if (!this.client) {
346
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
440
347
  }
441
- for (const message of messages) {
442
- if (!message.id) {
443
- throw new Error("Message ID is required");
444
- }
445
- if (!message.threadId) {
446
- throw new Error("Thread ID is required for all messages");
447
- }
448
- if (message.resourceId === null || message.resourceId === void 0) {
449
- throw new Error("Resource ID cannot be null or undefined");
450
- }
451
- if (!message.content) {
452
- throw new Error("Message content is required");
453
- }
348
+ if (!tableName) {
349
+ throw new Error("tableName is required for clearTable.");
454
350
  }
455
- const transformedMessages = messages.map((message) => {
456
- const { threadId: threadId2, type, ...rest } = message;
457
- return {
458
- ...rest,
459
- thread_id: threadId2,
460
- type: type ?? "v2",
461
- content: JSON.stringify(message.content)
462
- };
463
- });
464
- const table = await this.client.openTable(storage.TABLE_MESSAGES);
465
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
466
- const threadsTable = await this.client.openTable(storage.TABLE_THREADS);
467
- const currentTime = (/* @__PURE__ */ new Date()).getTime();
468
- const updateRecord = { id: threadId, updatedAt: currentTime };
469
- await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([updateRecord]);
470
- const list = new agent.MessageList().add(messages, "memory");
471
- return { messages: list.get.all.db() };
351
+ } catch (validationError) {
352
+ throw new error.MastraError(
353
+ {
354
+ id: storage.createStorageErrorId("LANCE", "CLEAR_TABLE", "INVALID_ARGS"),
355
+ domain: error.ErrorDomain.STORAGE,
356
+ category: error.ErrorCategory.USER,
357
+ text: validationError.message,
358
+ details: { tableName }
359
+ },
360
+ validationError
361
+ );
362
+ }
363
+ try {
364
+ const table = await this.client.openTable(tableName);
365
+ await table.delete("1=1");
472
366
  } catch (error$1) {
473
367
  throw new error.MastraError(
474
368
  {
475
- id: storage.createStorageErrorId("LANCE", "SAVE_MESSAGES", "FAILED"),
369
+ id: storage.createStorageErrorId("LANCE", "CLEAR_TABLE", "FAILED"),
476
370
  domain: error.ErrorDomain.STORAGE,
477
- category: error.ErrorCategory.THIRD_PARTY
371
+ category: error.ErrorCategory.THIRD_PARTY,
372
+ details: { tableName }
478
373
  },
479
374
  error$1
480
375
  );
481
376
  }
482
377
  }
483
- async listThreadsByResourceId(args) {
378
+ async insert({ tableName, record }) {
484
379
  try {
485
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
486
- const perPage = storage.normalizePerPage(perPageInput, 100);
487
- if (page < 0) {
488
- throw new error.MastraError(
489
- {
490
- id: storage.createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
491
- domain: error.ErrorDomain.STORAGE,
492
- category: error.ErrorCategory.USER,
493
- details: { page }
494
- },
495
- new Error("page must be >= 0")
496
- );
380
+ if (!this.client) {
381
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
497
382
  }
498
- const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
499
- const { field, direction } = this.parseOrderBy(orderBy);
500
- const table = await this.client.openTable(storage.TABLE_THREADS);
501
- const total = await table.countRows(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
502
- const query = table.query().where(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
503
- const records = await query.toArray();
504
- records.sort((a, b) => {
505
- const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
506
- const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
507
- if (aValue == null && bValue == null) return 0;
508
- if (aValue == null) return direction === "ASC" ? -1 : 1;
509
- if (bValue == null) return direction === "ASC" ? 1 : -1;
510
- if (typeof aValue === "string" && typeof bValue === "string") {
511
- return direction === "ASC" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
512
- }
513
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
514
- });
515
- const paginatedRecords = records.slice(offset, offset + perPage);
516
- const schema = await getTableSchema({ tableName: storage.TABLE_THREADS, client: this.client });
517
- const threads = paginatedRecords.map(
518
- (record) => processResultWithTypeConversion(record, schema)
519
- );
520
- return {
521
- threads,
522
- total,
523
- page,
524
- perPage: perPageForResponse,
525
- hasMore: offset + perPage < total
526
- };
527
- } catch (error$1) {
383
+ if (!tableName) {
384
+ throw new Error("tableName is required for insert.");
385
+ }
386
+ if (!record || Object.keys(record).length === 0) {
387
+ throw new Error("record is required and cannot be empty for insert.");
388
+ }
389
+ } catch (validationError) {
528
390
  throw new error.MastraError(
529
391
  {
530
- id: storage.createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
392
+ id: storage.createStorageErrorId("LANCE", "INSERT", "INVALID_ARGS"),
531
393
  domain: error.ErrorDomain.STORAGE,
532
- category: error.ErrorCategory.THIRD_PARTY
394
+ category: error.ErrorCategory.USER,
395
+ text: validationError.message,
396
+ details: { tableName }
533
397
  },
534
- error$1
398
+ validationError
535
399
  );
536
400
  }
537
- }
538
- /**
539
- * Processes messages to include context messages based on withPreviousMessages and withNextMessages
540
- * @param records - The sorted array of records to process
541
- * @param include - The array of include specifications with context parameters
542
- * @returns The processed array with context messages included
543
- */
544
- processMessagesWithContext(records, include) {
545
- const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
546
- if (messagesWithContext.length === 0) {
547
- return records;
548
- }
549
- const messageIndexMap = /* @__PURE__ */ new Map();
550
- records.forEach((message, index) => {
551
- messageIndexMap.set(message.id, index);
552
- });
553
- const additionalIndices = /* @__PURE__ */ new Set();
554
- for (const item of messagesWithContext) {
555
- const messageIndex = messageIndexMap.get(item.id);
556
- if (messageIndex !== void 0) {
557
- if (item.withPreviousMessages) {
558
- const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
559
- for (let i = startIdx; i < messageIndex; i++) {
560
- additionalIndices.add(i);
561
- }
562
- }
563
- if (item.withNextMessages) {
564
- const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
565
- for (let i = messageIndex + 1; i <= endIdx; i++) {
566
- additionalIndices.add(i);
567
- }
568
- }
569
- }
570
- }
571
- if (additionalIndices.size === 0) {
572
- return records;
573
- }
574
- const originalMatchIds = new Set(include.map((item) => item.id));
575
- const allIndices = /* @__PURE__ */ new Set();
576
- records.forEach((record, index) => {
577
- if (originalMatchIds.has(record.id)) {
578
- allIndices.add(index);
579
- }
580
- });
581
- additionalIndices.forEach((index) => {
582
- allIndices.add(index);
583
- });
584
- return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
585
- }
586
- /**
587
- * Parse message data from LanceDB record format to MastraDBMessage format
588
- */
589
- parseMessageData(data) {
590
- const { thread_id, ...rest } = data;
591
- return {
592
- ...rest,
593
- threadId: thread_id,
594
- content: typeof data.content === "string" ? (() => {
595
- try {
596
- return JSON.parse(data.content);
597
- } catch {
598
- return data.content;
599
- }
600
- })() : data.content,
601
- createdAt: new Date(data.createdAt),
602
- updatedAt: new Date(data.updatedAt)
603
- };
604
- }
605
- async updateMessages(args) {
606
- const { messages } = args;
607
- this.logger.debug("Updating messages", { count: messages.length });
608
- if (!messages.length) {
609
- return [];
610
- }
611
- const updatedMessages = [];
612
- const affectedThreadIds = /* @__PURE__ */ new Set();
613
401
  try {
614
- for (const updateData of messages) {
615
- const { id, ...updates } = updateData;
616
- const existingMessage = await this.operations.load({ tableName: storage.TABLE_MESSAGES, keys: { id } });
617
- if (!existingMessage) {
618
- this.logger.warn("Message not found for update", { id });
619
- continue;
620
- }
621
- const existingMsg = this.parseMessageData(existingMessage);
622
- const originalThreadId = existingMsg.threadId;
623
- affectedThreadIds.add(originalThreadId);
624
- const updatePayload = {};
625
- if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
626
- if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
627
- if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
628
- if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
629
- updatePayload.thread_id = updates.threadId;
630
- affectedThreadIds.add(updates.threadId);
631
- }
632
- if (updates.content) {
633
- const existingContent = existingMsg.content;
634
- let newContent = { ...existingContent };
635
- if (updates.content.metadata !== void 0) {
636
- newContent.metadata = {
637
- ...existingContent.metadata || {},
638
- ...updates.content.metadata || {}
639
- };
640
- }
641
- if (updates.content.content !== void 0) {
642
- newContent.content = updates.content.content;
643
- }
644
- if ("parts" in updates.content && updates.content.parts !== void 0) {
645
- newContent.parts = updates.content.parts;
646
- }
647
- updatePayload.content = JSON.stringify(newContent);
648
- }
649
- await this.operations.insert({ tableName: storage.TABLE_MESSAGES, record: { id, ...updatePayload } });
650
- const updatedMessage = await this.operations.load({ tableName: storage.TABLE_MESSAGES, keys: { id } });
651
- if (updatedMessage) {
652
- updatedMessages.push(this.parseMessageData(updatedMessage));
402
+ const table = await this.client.openTable(tableName);
403
+ const primaryId = getPrimaryKeys(tableName);
404
+ const processedRecord = { ...record };
405
+ for (const key in processedRecord) {
406
+ if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
407
+ this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
408
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
653
409
  }
654
410
  }
655
- for (const threadId of affectedThreadIds) {
656
- await this.operations.insert({
657
- tableName: storage.TABLE_THREADS,
658
- record: { id: threadId, updatedAt: Date.now() }
659
- });
660
- }
661
- return updatedMessages;
411
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
662
412
  } catch (error$1) {
663
413
  throw new error.MastraError(
664
414
  {
665
- id: storage.createStorageErrorId("LANCE", "UPDATE_MESSAGES", "FAILED"),
415
+ id: storage.createStorageErrorId("LANCE", "INSERT", "FAILED"),
666
416
  domain: error.ErrorDomain.STORAGE,
667
417
  category: error.ErrorCategory.THIRD_PARTY,
668
- details: { count: messages.length }
418
+ details: { tableName }
669
419
  },
670
420
  error$1
671
421
  );
672
422
  }
673
423
  }
674
- async getResourceById({ resourceId }) {
424
+ async batchInsert({ tableName, records }) {
675
425
  try {
676
- const resource = await this.operations.load({ tableName: storage.TABLE_RESOURCES, keys: { id: resourceId } });
677
- if (!resource) {
678
- return null;
679
- }
680
- let createdAt;
681
- let updatedAt;
682
- try {
683
- if (resource.createdAt instanceof Date) {
684
- createdAt = resource.createdAt;
685
- } else if (typeof resource.createdAt === "string") {
686
- createdAt = new Date(resource.createdAt);
687
- } else if (typeof resource.createdAt === "number") {
688
- createdAt = new Date(resource.createdAt);
689
- } else {
690
- createdAt = /* @__PURE__ */ new Date();
691
- }
692
- if (isNaN(createdAt.getTime())) {
693
- createdAt = /* @__PURE__ */ new Date();
694
- }
695
- } catch {
696
- createdAt = /* @__PURE__ */ new Date();
697
- }
698
- try {
699
- if (resource.updatedAt instanceof Date) {
700
- updatedAt = resource.updatedAt;
701
- } else if (typeof resource.updatedAt === "string") {
702
- updatedAt = new Date(resource.updatedAt);
703
- } else if (typeof resource.updatedAt === "number") {
704
- updatedAt = new Date(resource.updatedAt);
705
- } else {
706
- updatedAt = /* @__PURE__ */ new Date();
707
- }
708
- if (isNaN(updatedAt.getTime())) {
709
- updatedAt = /* @__PURE__ */ new Date();
710
- }
711
- } catch {
712
- updatedAt = /* @__PURE__ */ new Date();
426
+ if (!this.client) {
427
+ throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
713
428
  }
714
- let workingMemory = resource.workingMemory;
715
- if (workingMemory === null || workingMemory === void 0) {
716
- workingMemory = void 0;
717
- } else if (workingMemory === "") {
718
- workingMemory = "";
719
- } else if (typeof workingMemory === "object") {
720
- workingMemory = JSON.stringify(workingMemory);
429
+ if (!tableName) {
430
+ throw new Error("tableName is required for batchInsert.");
721
431
  }
722
- let metadata = resource.metadata;
723
- if (metadata === "" || metadata === null || metadata === void 0) {
724
- metadata = void 0;
725
- } else if (typeof metadata === "string") {
726
- try {
727
- metadata = JSON.parse(metadata);
728
- } catch {
729
- metadata = metadata;
730
- }
432
+ if (!records || records.length === 0) {
433
+ throw new Error("records array is required and cannot be empty for batchInsert.");
731
434
  }
732
- return {
733
- ...resource,
734
- createdAt,
735
- updatedAt,
736
- workingMemory,
737
- metadata
738
- };
739
- } catch (error$1) {
435
+ } catch (validationError) {
740
436
  throw new error.MastraError(
741
437
  {
742
- id: storage.createStorageErrorId("LANCE", "GET_RESOURCE_BY_ID", "FAILED"),
438
+ id: storage.createStorageErrorId("LANCE", "BATCH_INSERT", "INVALID_ARGS"),
743
439
  domain: error.ErrorDomain.STORAGE,
744
- category: error.ErrorCategory.THIRD_PARTY
440
+ category: error.ErrorCategory.USER,
441
+ text: validationError.message,
442
+ details: { tableName }
745
443
  },
746
- error$1
444
+ validationError
747
445
  );
748
446
  }
749
- }
750
- async saveResource({ resource }) {
751
447
  try {
752
- const record = {
753
- ...resource,
754
- metadata: resource.metadata ? JSON.stringify(resource.metadata) : "",
755
- createdAt: resource.createdAt.getTime(),
756
- // Store as timestamp (milliseconds)
757
- updatedAt: resource.updatedAt.getTime()
758
- // Store as timestamp (milliseconds)
759
- };
760
- const table = await this.client.openTable(storage.TABLE_RESOURCES);
761
- await table.add([record], { mode: "append" });
762
- return resource;
448
+ const table = await this.client.openTable(tableName);
449
+ const primaryId = getPrimaryKeys(tableName);
450
+ const processedRecords = records.map((record) => {
451
+ const processedRecord = { ...record };
452
+ for (const key in processedRecord) {
453
+ if (processedRecord[key] == null) continue;
454
+ if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
455
+ processedRecord[key] = JSON.stringify(processedRecord[key]);
456
+ }
457
+ }
458
+ return processedRecord;
459
+ });
460
+ await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
763
461
  } catch (error$1) {
764
462
  throw new error.MastraError(
765
463
  {
766
- id: storage.createStorageErrorId("LANCE", "SAVE_RESOURCE", "FAILED"),
464
+ id: storage.createStorageErrorId("LANCE", "BATCH_INSERT", "FAILED"),
767
465
  domain: error.ErrorDomain.STORAGE,
768
- category: error.ErrorCategory.THIRD_PARTY
466
+ category: error.ErrorCategory.THIRD_PARTY,
467
+ details: { tableName }
769
468
  },
770
469
  error$1
771
470
  );
772
471
  }
773
472
  }
774
- async updateResource({
775
- resourceId,
776
- workingMemory,
777
- metadata
778
- }) {
779
- const maxRetries = 3;
780
- for (let attempt = 0; attempt < maxRetries; attempt++) {
781
- try {
782
- const existingResource = await this.getResourceById({ resourceId });
783
- if (!existingResource) {
784
- const newResource = {
785
- id: resourceId,
786
- workingMemory,
787
- metadata: metadata || {},
788
- createdAt: /* @__PURE__ */ new Date(),
789
- updatedAt: /* @__PURE__ */ new Date()
790
- };
791
- return this.saveResource({ resource: newResource });
792
- }
793
- const updatedResource = {
794
- ...existingResource,
795
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
796
- metadata: {
797
- ...existingResource.metadata,
798
- ...metadata
799
- },
800
- updatedAt: /* @__PURE__ */ new Date()
801
- };
802
- const record = {
803
- id: resourceId,
804
- workingMemory: updatedResource.workingMemory || "",
805
- metadata: updatedResource.metadata ? JSON.stringify(updatedResource.metadata) : "",
806
- updatedAt: updatedResource.updatedAt.getTime()
807
- // Store as timestamp (milliseconds)
808
- };
809
- const table = await this.client.openTable(storage.TABLE_RESOURCES);
810
- await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
811
- return updatedResource;
812
- } catch (error$1) {
813
- if (error$1.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
814
- const delay = Math.pow(2, attempt) * 10;
815
- await new Promise((resolve) => setTimeout(resolve, delay));
816
- continue;
817
- }
818
- throw new error.MastraError(
819
- {
820
- id: storage.createStorageErrorId("LANCE", "UPDATE_RESOURCE", "FAILED"),
821
- domain: error.ErrorDomain.STORAGE,
822
- category: error.ErrorCategory.THIRD_PARTY
823
- },
824
- error$1
825
- );
826
- }
827
- }
828
- throw new Error("Unexpected end of retry loop");
829
- }
830
- };
831
- var StoreOperationsLance = class extends storage.StoreOperations {
832
- client;
833
- constructor({ client }) {
834
- super();
835
- this.client = client;
836
- }
837
- getDefaultValue(type) {
838
- switch (type) {
839
- case "text":
840
- return "''";
841
- case "timestamp":
842
- return "CURRENT_TIMESTAMP";
843
- case "integer":
844
- case "bigint":
845
- return "0";
846
- case "jsonb":
847
- return "'{}'";
848
- case "uuid":
849
- return "''";
850
- default:
851
- return super.getDefaultValue(type);
852
- }
853
- }
854
- async hasColumn(tableName, columnName) {
855
- const table = await this.client.openTable(tableName);
856
- const schema = await table.schema();
857
- return schema.fields.some((field) => field.name === columnName);
858
- }
859
- translateSchema(schema) {
860
- const fields = Object.entries(schema).map(([name, column]) => {
861
- let arrowType;
862
- switch (column.type.toLowerCase()) {
863
- case "text":
864
- case "uuid":
865
- arrowType = new apacheArrow.Utf8();
866
- break;
867
- case "int":
868
- case "integer":
869
- arrowType = new apacheArrow.Int32();
870
- break;
871
- case "bigint":
872
- arrowType = new apacheArrow.Float64();
873
- break;
874
- case "float":
875
- arrowType = new apacheArrow.Float32();
876
- break;
877
- case "jsonb":
878
- case "json":
879
- arrowType = new apacheArrow.Utf8();
880
- break;
881
- case "binary":
882
- arrowType = new apacheArrow.Binary();
883
- break;
884
- case "timestamp":
885
- arrowType = new apacheArrow.Float64();
886
- break;
887
- default:
888
- arrowType = new apacheArrow.Utf8();
889
- }
890
- return new apacheArrow.Field(name, arrowType, column.nullable ?? true);
891
- });
892
- return new apacheArrow.Schema(fields);
893
- }
894
- async createTable({
895
- tableName,
896
- schema
897
- }) {
473
+ async load({ tableName, keys }) {
898
474
  try {
899
475
  if (!this.client) {
900
476
  throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
901
477
  }
902
478
  if (!tableName) {
903
- throw new Error("tableName is required for createTable.");
479
+ throw new Error("tableName is required for load.");
904
480
  }
905
- if (!schema) {
906
- throw new Error("schema is required for createTable.");
481
+ if (!keys || Object.keys(keys).length === 0) {
482
+ throw new Error("keys are required and cannot be empty for load.");
907
483
  }
908
- } catch (error$1) {
484
+ } catch (validationError) {
909
485
  throw new error.MastraError(
910
486
  {
911
- id: storage.createStorageErrorId("LANCE", "CREATE_TABLE", "INVALID_ARGS"),
487
+ id: storage.createStorageErrorId("LANCE", "LOAD", "INVALID_ARGS"),
912
488
  domain: error.ErrorDomain.STORAGE,
913
489
  category: error.ErrorCategory.USER,
490
+ text: validationError.message,
914
491
  details: { tableName }
915
492
  },
916
- error$1
493
+ validationError
917
494
  );
918
495
  }
919
496
  try {
920
- const arrowSchema = this.translateSchema(schema);
921
- await this.client.createEmptyTable(tableName, arrowSchema);
922
- } catch (error$1) {
923
- if (error$1.message?.includes("already exists")) {
924
- this.logger.debug(`Table '${tableName}' already exists, skipping create`);
925
- return;
497
+ const table = await this.client.openTable(tableName);
498
+ const tableSchema = await getTableSchema({ tableName, client: this.client });
499
+ const query = table.query();
500
+ if (Object.keys(keys).length > 0) {
501
+ validateKeyTypes(keys, tableSchema);
502
+ const filterConditions = Object.entries(keys).map(([key, value]) => {
503
+ const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
504
+ const quotedKey = isCamelCase ? `\`${key}\`` : key;
505
+ if (typeof value === "string") {
506
+ return `${quotedKey} = '${value}'`;
507
+ } else if (value === null) {
508
+ return `${quotedKey} IS NULL`;
509
+ } else {
510
+ return `${quotedKey} = ${value}`;
511
+ }
512
+ }).join(" AND ");
513
+ this.logger.debug("where clause generated: " + filterConditions);
514
+ query.where(filterConditions);
515
+ }
516
+ const result = await query.limit(1).toArray();
517
+ if (result.length === 0) {
518
+ this.logger.debug("No record found");
519
+ return null;
926
520
  }
521
+ return processResultWithTypeConversion(result[0], tableSchema);
522
+ } catch (error$1) {
523
+ if (error$1 instanceof error.MastraError) throw error$1;
927
524
  throw new error.MastraError(
928
525
  {
929
- id: storage.createStorageErrorId("LANCE", "CREATE_TABLE", "FAILED"),
526
+ id: storage.createStorageErrorId("LANCE", "LOAD", "FAILED"),
930
527
  domain: error.ErrorDomain.STORAGE,
931
528
  category: error.ErrorCategory.THIRD_PARTY,
932
- details: { tableName }
529
+ details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
933
530
  },
934
531
  error$1
935
532
  );
936
533
  }
937
534
  }
938
- async dropTable({ tableName }) {
535
+ };
536
+
537
+ // src/storage/domains/memory/index.ts
538
+ var StoreMemoryLance = class extends storage.MemoryStorage {
539
+ client;
540
+ #db;
541
+ constructor(config) {
542
+ super();
543
+ const client = resolveLanceConfig(config);
544
+ this.client = client;
545
+ this.#db = new LanceDB({ client });
546
+ }
547
+ async init() {
548
+ await this.#db.createTable({ tableName: storage.TABLE_THREADS, schema: storage.TABLE_SCHEMAS[storage.TABLE_THREADS] });
549
+ await this.#db.createTable({ tableName: storage.TABLE_MESSAGES, schema: storage.TABLE_SCHEMAS[storage.TABLE_MESSAGES] });
550
+ await this.#db.createTable({ tableName: storage.TABLE_RESOURCES, schema: storage.TABLE_SCHEMAS[storage.TABLE_RESOURCES] });
551
+ await this.#db.alterTable({
552
+ tableName: storage.TABLE_MESSAGES,
553
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_MESSAGES],
554
+ ifNotExists: ["resourceId"]
555
+ });
556
+ }
557
+ async dangerouslyClearAll() {
558
+ await this.#db.clearTable({ tableName: storage.TABLE_THREADS });
559
+ await this.#db.clearTable({ tableName: storage.TABLE_MESSAGES });
560
+ await this.#db.clearTable({ tableName: storage.TABLE_RESOURCES });
561
+ }
562
+ async deleteMessages(messageIds) {
563
+ if (!messageIds || messageIds.length === 0) {
564
+ return;
565
+ }
566
+ this.logger.debug("Deleting messages", { count: messageIds.length });
939
567
  try {
940
- if (!this.client) {
941
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
568
+ const threadIds = /* @__PURE__ */ new Set();
569
+ for (const messageId of messageIds) {
570
+ const message = await this.#db.load({ tableName: storage.TABLE_MESSAGES, keys: { id: messageId } });
571
+ if (message?.thread_id) {
572
+ threadIds.add(message.thread_id);
573
+ }
942
574
  }
943
- if (!tableName) {
944
- throw new Error("tableName is required for dropTable.");
575
+ const messagesTable = await this.client.openTable(storage.TABLE_MESSAGES);
576
+ const idConditions = messageIds.map((id) => `id = '${this.escapeSql(id)}'`).join(" OR ");
577
+ await messagesTable.delete(idConditions);
578
+ const now = (/* @__PURE__ */ new Date()).getTime();
579
+ const threadsTable = await this.client.openTable(storage.TABLE_THREADS);
580
+ for (const threadId of threadIds) {
581
+ const thread = await this.getThreadById({ threadId });
582
+ if (thread) {
583
+ const record = {
584
+ id: threadId,
585
+ resourceId: thread.resourceId,
586
+ title: thread.title,
587
+ metadata: JSON.stringify(thread.metadata),
588
+ createdAt: new Date(thread.createdAt).getTime(),
589
+ updatedAt: now
590
+ };
591
+ await threadsTable.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
592
+ }
945
593
  }
946
- } catch (validationError) {
594
+ } catch (error$1) {
947
595
  throw new error.MastraError(
948
596
  {
949
- id: storage.createStorageErrorId("LANCE", "DROP_TABLE", "INVALID_ARGS"),
597
+ id: storage.createStorageErrorId("LANCE", "DELETE_MESSAGES", "FAILED"),
950
598
  domain: error.ErrorDomain.STORAGE,
951
- category: error.ErrorCategory.USER,
952
- text: validationError.message,
953
- details: { tableName }
599
+ category: error.ErrorCategory.THIRD_PARTY,
600
+ details: { count: messageIds.length }
954
601
  },
955
- validationError
602
+ error$1
956
603
  );
957
604
  }
605
+ }
606
+ // Utility to escape single quotes in SQL strings
607
+ escapeSql(str) {
608
+ return str.replace(/'/g, "''");
609
+ }
610
+ async getThreadById({ threadId }) {
958
611
  try {
959
- await this.client.dropTable(tableName);
960
- } catch (error$1) {
961
- if (error$1.toString().includes("was not found") || error$1.message?.includes("Table not found")) {
962
- this.logger.debug(`Table '${tableName}' does not exist, skipping drop`);
963
- return;
612
+ const thread = await this.#db.load({ tableName: storage.TABLE_THREADS, keys: { id: threadId } });
613
+ if (!thread) {
614
+ return null;
964
615
  }
616
+ return {
617
+ ...thread,
618
+ createdAt: new Date(thread.createdAt),
619
+ updatedAt: new Date(thread.updatedAt)
620
+ };
621
+ } catch (error$1) {
965
622
  throw new error.MastraError(
966
623
  {
967
- id: storage.createStorageErrorId("LANCE", "DROP_TABLE", "FAILED"),
624
+ id: storage.createStorageErrorId("LANCE", "GET_THREAD_BY_ID", "FAILED"),
968
625
  domain: error.ErrorDomain.STORAGE,
969
- category: error.ErrorCategory.THIRD_PARTY,
970
- details: { tableName }
626
+ category: error.ErrorCategory.THIRD_PARTY
971
627
  },
972
628
  error$1
973
629
  );
974
630
  }
975
631
  }
976
- async alterTable({
977
- tableName,
978
- schema,
979
- ifNotExists
980
- }) {
632
+ /**
633
+ * Saves a thread to the database. This function doesn't overwrite existing threads.
634
+ * @param thread - The thread to save
635
+ * @returns The saved thread
636
+ */
637
+ async saveThread({ thread }) {
981
638
  try {
982
- if (!this.client) {
983
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
984
- }
985
- if (!tableName) {
986
- throw new Error("tableName is required for alterTable.");
987
- }
988
- if (!schema) {
989
- throw new Error("schema is required for alterTable.");
990
- }
991
- if (!ifNotExists || ifNotExists.length === 0) {
992
- this.logger.debug("No columns specified to add in alterTable, skipping.");
993
- return;
994
- }
995
- } catch (validationError) {
639
+ const record = { ...thread, metadata: JSON.stringify(thread.metadata) };
640
+ const table = await this.client.openTable(storage.TABLE_THREADS);
641
+ await table.add([record], { mode: "append" });
642
+ return thread;
643
+ } catch (error$1) {
996
644
  throw new error.MastraError(
997
645
  {
998
- id: storage.createStorageErrorId("LANCE", "ALTER_TABLE", "INVALID_ARGS"),
646
+ id: storage.createStorageErrorId("LANCE", "SAVE_THREAD", "FAILED"),
999
647
  domain: error.ErrorDomain.STORAGE,
1000
- category: error.ErrorCategory.USER,
1001
- text: validationError.message,
1002
- details: { tableName }
648
+ category: error.ErrorCategory.THIRD_PARTY
1003
649
  },
1004
- validationError
650
+ error$1
1005
651
  );
1006
652
  }
1007
- try {
1008
- const table = await this.client.openTable(tableName);
1009
- const currentSchema = await table.schema();
1010
- const existingFields = new Set(currentSchema.fields.map((f) => f.name));
1011
- const typeMap = {
1012
- text: "string",
1013
- integer: "int",
1014
- bigint: "bigint",
1015
- timestamp: "timestamp",
1016
- jsonb: "string",
1017
- uuid: "string"
1018
- };
1019
- const columnsToAdd = ifNotExists.filter((col) => schema[col] && !existingFields.has(col)).map((col) => {
1020
- const colDef = schema[col];
1021
- return {
1022
- name: col,
1023
- valueSql: colDef?.nullable ? `cast(NULL as ${typeMap[colDef.type ?? "text"]})` : `cast(${this.getDefaultValue(colDef?.type ?? "text")} as ${typeMap[colDef?.type ?? "text"]})`
653
+ }
654
+ async updateThread({
655
+ id,
656
+ title,
657
+ metadata
658
+ }) {
659
+ const maxRetries = 5;
660
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
661
+ try {
662
+ const current = await this.getThreadById({ threadId: id });
663
+ if (!current) {
664
+ throw new Error(`Thread with id ${id} not found`);
665
+ }
666
+ const mergedMetadata = { ...current.metadata, ...metadata };
667
+ const record = {
668
+ id,
669
+ title,
670
+ metadata: JSON.stringify(mergedMetadata),
671
+ updatedAt: (/* @__PURE__ */ new Date()).getTime()
1024
672
  };
1025
- });
1026
- if (columnsToAdd.length > 0) {
1027
- await table.addColumns(columnsToAdd);
1028
- this.logger?.info?.(`Added columns [${columnsToAdd.map((c) => c.name).join(", ")}] to table ${tableName}`);
673
+ const table = await this.client.openTable(storage.TABLE_THREADS);
674
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
675
+ const updatedThread = await this.getThreadById({ threadId: id });
676
+ if (!updatedThread) {
677
+ throw new Error(`Failed to retrieve updated thread ${id}`);
678
+ }
679
+ return updatedThread;
680
+ } catch (error$1) {
681
+ if (error$1.message?.includes("Commit conflict") && attempt < maxRetries - 1) {
682
+ const delay = Math.pow(2, attempt) * 10;
683
+ await new Promise((resolve) => setTimeout(resolve, delay));
684
+ continue;
685
+ }
686
+ throw new error.MastraError(
687
+ {
688
+ id: storage.createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
689
+ domain: error.ErrorDomain.STORAGE,
690
+ category: error.ErrorCategory.THIRD_PARTY
691
+ },
692
+ error$1
693
+ );
1029
694
  }
1030
- } catch (error$1) {
1031
- throw new error.MastraError(
1032
- {
1033
- id: storage.createStorageErrorId("LANCE", "ALTER_TABLE", "FAILED"),
1034
- domain: error.ErrorDomain.STORAGE,
1035
- category: error.ErrorCategory.THIRD_PARTY,
1036
- details: { tableName }
1037
- },
1038
- error$1
1039
- );
1040
695
  }
696
+ throw new error.MastraError(
697
+ {
698
+ id: storage.createStorageErrorId("LANCE", "UPDATE_THREAD", "FAILED"),
699
+ domain: error.ErrorDomain.STORAGE,
700
+ category: error.ErrorCategory.THIRD_PARTY
701
+ },
702
+ new Error("All retries exhausted")
703
+ );
1041
704
  }
1042
- async clearTable({ tableName }) {
1043
- try {
1044
- if (!this.client) {
1045
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1046
- }
1047
- if (!tableName) {
1048
- throw new Error("tableName is required for clearTable.");
1049
- }
1050
- } catch (validationError) {
1051
- throw new error.MastraError(
1052
- {
1053
- id: storage.createStorageErrorId("LANCE", "CLEAR_TABLE", "INVALID_ARGS"),
1054
- domain: error.ErrorDomain.STORAGE,
1055
- category: error.ErrorCategory.USER,
1056
- text: validationError.message,
1057
- details: { tableName }
1058
- },
1059
- validationError
1060
- );
1061
- }
705
+ async deleteThread({ threadId }) {
1062
706
  try {
1063
- const table = await this.client.openTable(tableName);
1064
- await table.delete("1=1");
707
+ const table = await this.client.openTable(storage.TABLE_THREADS);
708
+ await table.delete(`id = '${threadId}'`);
709
+ const messagesTable = await this.client.openTable(storage.TABLE_MESSAGES);
710
+ await messagesTable.delete(`thread_id = '${threadId}'`);
1065
711
  } catch (error$1) {
1066
712
  throw new error.MastraError(
1067
713
  {
1068
- id: storage.createStorageErrorId("LANCE", "CLEAR_TABLE", "FAILED"),
714
+ id: storage.createStorageErrorId("LANCE", "DELETE_THREAD", "FAILED"),
1069
715
  domain: error.ErrorDomain.STORAGE,
1070
- category: error.ErrorCategory.THIRD_PARTY,
1071
- details: { tableName }
716
+ category: error.ErrorCategory.THIRD_PARTY
1072
717
  },
1073
718
  error$1
1074
719
  );
1075
720
  }
1076
721
  }
1077
- async insert({ tableName, record }) {
722
+ normalizeMessage(message) {
723
+ const { thread_id, ...rest } = message;
724
+ return {
725
+ ...rest,
726
+ threadId: thread_id,
727
+ content: typeof message.content === "string" ? (() => {
728
+ try {
729
+ return JSON.parse(message.content);
730
+ } catch {
731
+ return message.content;
732
+ }
733
+ })() : message.content
734
+ };
735
+ }
736
+ async listMessagesById({ messageIds }) {
737
+ if (messageIds.length === 0) return { messages: [] };
1078
738
  try {
1079
- if (!this.client) {
1080
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1081
- }
1082
- if (!tableName) {
1083
- throw new Error("tableName is required for insert.");
1084
- }
1085
- if (!record || Object.keys(record).length === 0) {
1086
- throw new Error("record is required and cannot be empty for insert.");
1087
- }
1088
- } catch (validationError) {
1089
- throw new error.MastraError(
1090
- {
1091
- id: storage.createStorageErrorId("LANCE", "INSERT", "INVALID_ARGS"),
1092
- domain: error.ErrorDomain.STORAGE,
1093
- category: error.ErrorCategory.USER,
1094
- text: validationError.message,
1095
- details: { tableName }
1096
- },
1097
- validationError
739
+ const table = await this.client.openTable(storage.TABLE_MESSAGES);
740
+ const quotedIds = messageIds.map((id) => `'${id}'`).join(", ");
741
+ const allRecords = await table.query().where(`id IN (${quotedIds})`).toArray();
742
+ const messages = processResultWithTypeConversion(
743
+ allRecords,
744
+ await getTableSchema({ tableName: storage.TABLE_MESSAGES, client: this.client })
1098
745
  );
1099
- }
1100
- try {
1101
- const table = await this.client.openTable(tableName);
1102
- const primaryId = getPrimaryKeys(tableName);
1103
- const processedRecord = { ...record };
1104
- for (const key in processedRecord) {
1105
- if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
1106
- this.logger.debug("Converting object to JSON string: ", processedRecord[key]);
1107
- processedRecord[key] = JSON.stringify(processedRecord[key]);
1108
- }
1109
- }
1110
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([processedRecord]);
746
+ const list = new agent.MessageList().add(
747
+ messages.map(this.normalizeMessage),
748
+ "memory"
749
+ );
750
+ return { messages: list.get.all.db() };
1111
751
  } catch (error$1) {
1112
752
  throw new error.MastraError(
1113
753
  {
1114
- id: storage.createStorageErrorId("LANCE", "INSERT", "FAILED"),
754
+ id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES_BY_ID", "FAILED"),
1115
755
  domain: error.ErrorDomain.STORAGE,
1116
756
  category: error.ErrorCategory.THIRD_PARTY,
1117
- details: { tableName }
757
+ details: {
758
+ messageIds: JSON.stringify(messageIds)
759
+ }
1118
760
  },
1119
761
  error$1
1120
762
  );
1121
763
  }
1122
764
  }
1123
- async batchInsert({ tableName, records }) {
1124
- try {
1125
- if (!this.client) {
1126
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
1127
- }
1128
- if (!tableName) {
1129
- throw new Error("tableName is required for batchInsert.");
1130
- }
1131
- if (!records || records.length === 0) {
1132
- throw new Error("records array is required and cannot be empty for batchInsert.");
1133
- }
1134
- } catch (validationError) {
1135
- throw new error.MastraError(
1136
- {
1137
- id: storage.createStorageErrorId("LANCE", "BATCH_INSERT", "INVALID_ARGS"),
1138
- domain: error.ErrorDomain.STORAGE,
1139
- category: error.ErrorCategory.USER,
1140
- text: validationError.message,
1141
- details: { tableName }
1142
- },
1143
- validationError
1144
- );
1145
- }
1146
- try {
1147
- const table = await this.client.openTable(tableName);
1148
- const primaryId = getPrimaryKeys(tableName);
1149
- const processedRecords = records.map((record) => {
1150
- const processedRecord = { ...record };
1151
- for (const key in processedRecord) {
1152
- if (processedRecord[key] == null) continue;
1153
- if (processedRecord[key] !== null && typeof processedRecord[key] === "object" && !(processedRecord[key] instanceof Date)) {
1154
- processedRecord[key] = JSON.stringify(processedRecord[key]);
1155
- }
1156
- }
1157
- return processedRecord;
1158
- });
1159
- await table.mergeInsert(primaryId).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(processedRecords);
1160
- } catch (error$1) {
765
+ async listMessages(args) {
766
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
767
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
768
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1161
769
  throw new error.MastraError(
1162
770
  {
1163
- id: storage.createStorageErrorId("LANCE", "BATCH_INSERT", "FAILED"),
771
+ id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_THREAD_ID"),
1164
772
  domain: error.ErrorDomain.STORAGE,
1165
773
  category: error.ErrorCategory.THIRD_PARTY,
1166
- details: { tableName }
774
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1167
775
  },
1168
- error$1
776
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1169
777
  );
1170
778
  }
1171
- }
1172
- async load({ tableName, keys }) {
779
+ const perPage = storage.normalizePerPage(perPageInput, 40);
780
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1173
781
  try {
1174
- if (!this.client) {
1175
- throw new Error("LanceDB client not initialized. Call LanceStorage.create() first.");
782
+ if (page < 0) {
783
+ throw new error.MastraError(
784
+ {
785
+ id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "INVALID_PAGE"),
786
+ domain: error.ErrorDomain.STORAGE,
787
+ category: error.ErrorCategory.USER,
788
+ details: { page }
789
+ },
790
+ new Error("page must be >= 0")
791
+ );
1176
792
  }
1177
- if (!tableName) {
1178
- throw new Error("tableName is required for load.");
793
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
794
+ const table = await this.client.openTable(storage.TABLE_MESSAGES);
795
+ const threadCondition = threadIds.length === 1 ? `thread_id = '${this.escapeSql(threadIds[0])}'` : `thread_id IN (${threadIds.map((t) => `'${this.escapeSql(t)}'`).join(", ")})`;
796
+ const conditions = [threadCondition];
797
+ if (resourceId) {
798
+ conditions.push(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
1179
799
  }
1180
- if (!keys || Object.keys(keys).length === 0) {
1181
- throw new Error("keys are required and cannot be empty for load.");
800
+ if (filter?.dateRange?.start) {
801
+ const startTime = filter.dateRange.start instanceof Date ? filter.dateRange.start.getTime() : new Date(filter.dateRange.start).getTime();
802
+ conditions.push(`\`createdAt\` >= ${startTime}`);
1182
803
  }
1183
- } catch (validationError) {
1184
- throw new error.MastraError(
1185
- {
1186
- id: storage.createStorageErrorId("LANCE", "LOAD", "INVALID_ARGS"),
1187
- domain: error.ErrorDomain.STORAGE,
1188
- category: error.ErrorCategory.USER,
1189
- text: validationError.message,
1190
- details: { tableName }
1191
- },
1192
- validationError
1193
- );
1194
- }
1195
- try {
1196
- const table = await this.client.openTable(tableName);
1197
- const tableSchema = await getTableSchema({ tableName, client: this.client });
1198
- const query = table.query();
1199
- if (Object.keys(keys).length > 0) {
1200
- validateKeyTypes(keys, tableSchema);
1201
- const filterConditions = Object.entries(keys).map(([key, value]) => {
1202
- const isCamelCase = /^[a-z][a-zA-Z]*$/.test(key) && /[A-Z]/.test(key);
1203
- const quotedKey = isCamelCase ? `\`${key}\`` : key;
1204
- if (typeof value === "string") {
1205
- return `${quotedKey} = '${value}'`;
1206
- } else if (value === null) {
1207
- return `${quotedKey} IS NULL`;
1208
- } else {
1209
- return `${quotedKey} = ${value}`;
804
+ if (filter?.dateRange?.end) {
805
+ const endTime = filter.dateRange.end instanceof Date ? filter.dateRange.end.getTime() : new Date(filter.dateRange.end).getTime();
806
+ conditions.push(`\`createdAt\` <= ${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);
1210
850
  }
1211
- }).join(" AND ");
1212
- this.logger.debug("where clause generated: " + filterConditions);
1213
- query.where(filterConditions);
851
+ }
1214
852
  }
1215
- const result = await query.limit(1).toArray();
1216
- if (result.length === 0) {
1217
- this.logger.debug("No record found");
853
+ const list = new agent.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
+ };
877
+ } catch (error$1) {
878
+ const mastraError = new error.MastraError(
879
+ {
880
+ id: storage.createStorageErrorId("LANCE", "LIST_MESSAGES", "FAILED"),
881
+ domain: error.ErrorDomain.STORAGE,
882
+ category: error.ErrorCategory.THIRD_PARTY,
883
+ details: {
884
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
885
+ resourceId: resourceId ?? ""
886
+ }
887
+ },
888
+ error$1
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
+ };
899
+ }
900
+ }
901
+ async saveMessages(args) {
902
+ try {
903
+ const { messages } = args;
904
+ if (messages.length === 0) {
905
+ return { messages: [] };
906
+ }
907
+ const threadId = messages[0]?.threadId;
908
+ if (!threadId) {
909
+ throw new Error("Thread ID is required");
910
+ }
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
+ }
924
+ }
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(storage.TABLE_MESSAGES);
935
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(transformedMessages);
936
+ const threadsTable = await this.client.openTable(storage.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 agent.MessageList().add(messages, "memory");
941
+ return { messages: list.get.all.db() };
942
+ } catch (error$1) {
943
+ throw new error.MastraError(
944
+ {
945
+ id: storage.createStorageErrorId("LANCE", "SAVE_MESSAGES", "FAILED"),
946
+ domain: error.ErrorDomain.STORAGE,
947
+ category: error.ErrorCategory.THIRD_PARTY
948
+ },
949
+ error$1
950
+ );
951
+ }
952
+ }
953
+ async listThreadsByResourceId(args) {
954
+ try {
955
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
956
+ const perPage = storage.normalizePerPage(perPageInput, 100);
957
+ if (page < 0) {
958
+ throw new error.MastraError(
959
+ {
960
+ id: storage.createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
961
+ domain: error.ErrorDomain.STORAGE,
962
+ category: error.ErrorCategory.USER,
963
+ details: { page }
964
+ },
965
+ new Error("page must be >= 0")
966
+ );
967
+ }
968
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
969
+ const { field, direction } = this.parseOrderBy(orderBy);
970
+ const table = await this.client.openTable(storage.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: storage.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
+ };
997
+ } catch (error$1) {
998
+ throw new error.MastraError(
999
+ {
1000
+ id: storage.createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1001
+ domain: error.ErrorDomain.STORAGE,
1002
+ category: error.ErrorCategory.THIRD_PARTY
1003
+ },
1004
+ error$1
1005
+ );
1006
+ }
1007
+ }
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
+ }
1039
+ }
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);
1049
+ }
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: storage.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: storage.TABLE_MESSAGES, record: { id, ...updatePayload } });
1120
+ const updatedMessage = await this.#db.load({ tableName: storage.TABLE_MESSAGES, keys: { id } });
1121
+ if (updatedMessage) {
1122
+ updatedMessages.push(this.parseMessageData(updatedMessage));
1123
+ }
1124
+ }
1125
+ for (const threadId of affectedThreadIds) {
1126
+ await this.#db.insert({
1127
+ tableName: storage.TABLE_THREADS,
1128
+ record: { id: threadId, updatedAt: Date.now() }
1129
+ });
1130
+ }
1131
+ return updatedMessages;
1132
+ } catch (error$1) {
1133
+ throw new error.MastraError(
1134
+ {
1135
+ id: storage.createStorageErrorId("LANCE", "UPDATE_MESSAGES", "FAILED"),
1136
+ domain: error.ErrorDomain.STORAGE,
1137
+ category: error.ErrorCategory.THIRD_PARTY,
1138
+ details: { count: messages.length }
1139
+ },
1140
+ error$1
1141
+ );
1142
+ }
1143
+ }
1144
+ async getResourceById({ resourceId }) {
1145
+ try {
1146
+ const resource = await this.#db.load({ tableName: storage.TABLE_RESOURCES, keys: { id: resourceId } });
1147
+ if (!resource) {
1218
1148
  return null;
1219
1149
  }
1220
- return processResultWithTypeConversion(result[0], tableSchema);
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();
1177
+ }
1178
+ if (isNaN(updatedAt.getTime())) {
1179
+ updatedAt = /* @__PURE__ */ new Date();
1180
+ }
1181
+ } catch {
1182
+ updatedAt = /* @__PURE__ */ new Date();
1183
+ }
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);
1191
+ }
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
+ }
1201
+ }
1202
+ return {
1203
+ ...resource,
1204
+ createdAt,
1205
+ updatedAt,
1206
+ workingMemory,
1207
+ metadata
1208
+ };
1209
+ } catch (error$1) {
1210
+ throw new error.MastraError(
1211
+ {
1212
+ id: storage.createStorageErrorId("LANCE", "GET_RESOURCE_BY_ID", "FAILED"),
1213
+ domain: error.ErrorDomain.STORAGE,
1214
+ category: error.ErrorCategory.THIRD_PARTY
1215
+ },
1216
+ error$1
1217
+ );
1218
+ }
1219
+ }
1220
+ async saveResource({ resource }) {
1221
+ try {
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(storage.TABLE_RESOURCES);
1231
+ await table.add([record], { mode: "append" });
1232
+ return resource;
1221
1233
  } catch (error$1) {
1222
- if (error$1 instanceof error.MastraError) throw error$1;
1223
1234
  throw new error.MastraError(
1224
1235
  {
1225
- id: storage.createStorageErrorId("LANCE", "LOAD", "FAILED"),
1236
+ id: storage.createStorageErrorId("LANCE", "SAVE_RESOURCE", "FAILED"),
1226
1237
  domain: error.ErrorDomain.STORAGE,
1227
- category: error.ErrorCategory.THIRD_PARTY,
1228
- details: { tableName, keyCount: Object.keys(keys).length, firstKey: Object.keys(keys)[0] ?? "" }
1238
+ category: error.ErrorCategory.THIRD_PARTY
1229
1239
  },
1230
1240
  error$1
1231
1241
  );
1232
1242
  }
1233
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(storage.TABLE_RESOURCES);
1280
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
1281
+ return updatedResource;
1282
+ } catch (error$1) {
1283
+ if (error$1.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 error.MastraError(
1289
+ {
1290
+ id: storage.createStorageErrorId("LANCE", "UPDATE_RESOURCE", "FAILED"),
1291
+ domain: error.ErrorDomain.STORAGE,
1292
+ category: error.ErrorCategory.THIRD_PARTY
1293
+ },
1294
+ error$1
1295
+ );
1296
+ }
1297
+ }
1298
+ throw new Error("Unexpected end of retry loop");
1299
+ }
1234
1300
  };
1235
1301
  var StoreScoresLance = class extends storage.ScoresStorage {
1236
1302
  client;
1237
- constructor({ client }) {
1303
+ #db;
1304
+ constructor(config) {
1238
1305
  super();
1306
+ const client = resolveLanceConfig(config);
1239
1307
  this.client = client;
1308
+ this.#db = new LanceDB({ client });
1309
+ }
1310
+ async init() {
1311
+ await this.#db.createTable({ tableName: storage.TABLE_SCORERS, schema: storage.SCORERS_SCHEMA });
1312
+ await this.#db.alterTable({
1313
+ tableName: storage.TABLE_SCORERS,
1314
+ schema: storage.SCORERS_SCHEMA,
1315
+ ifNotExists: ["spanId", "requestContext"]
1316
+ });
1317
+ }
1318
+ async dangerouslyClearAll() {
1319
+ await this.#db.clearTable({ tableName: storage.TABLE_SCORERS });
1240
1320
  }
1241
1321
  async saveScore(score) {
1242
1322
  let validatedScore;
@@ -1250,7 +1330,7 @@ var StoreScoresLance = class extends storage.ScoresStorage {
1250
1330
  domain: error.ErrorDomain.STORAGE,
1251
1331
  category: error.ErrorCategory.USER,
1252
1332
  details: {
1253
- scorer: score.scorer?.id ?? "unknown",
1333
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
1254
1334
  entityId: score.entityId ?? "unknown",
1255
1335
  entityType: score.entityType ?? "unknown",
1256
1336
  traceId: score.traceId ?? "",
@@ -1520,6 +1600,9 @@ var StoreScoresLance = class extends storage.ScoresStorage {
1520
1600
  }
1521
1601
  }
1522
1602
  };
1603
+ function escapeSql(str) {
1604
+ return str.replace(/'/g, "''");
1605
+ }
1523
1606
  function parseWorkflowRun(row) {
1524
1607
  let parsedSnapshot = row.snapshot;
1525
1608
  if (typeof parsedSnapshot === "string") {
@@ -1540,42 +1623,88 @@ function parseWorkflowRun(row) {
1540
1623
  }
1541
1624
  var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1542
1625
  client;
1543
- constructor({ client }) {
1626
+ #db;
1627
+ constructor(config) {
1544
1628
  super();
1629
+ const client = resolveLanceConfig(config);
1545
1630
  this.client = client;
1631
+ this.#db = new LanceDB({ client });
1632
+ }
1633
+ async init() {
1634
+ const schema = storage.TABLE_SCHEMAS[storage.TABLE_WORKFLOW_SNAPSHOT];
1635
+ await this.#db.createTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, schema });
1636
+ await this.#db.alterTable({
1637
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
1638
+ schema,
1639
+ ifNotExists: ["resourceId"]
1640
+ });
1641
+ }
1642
+ async dangerouslyClearAll() {
1643
+ await this.#db.clearTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT });
1546
1644
  }
1547
- updateWorkflowResults({
1548
- // workflowName,
1549
- // runId,
1550
- // stepId,
1551
- // result,
1552
- // requestContext,
1645
+ async updateWorkflowResults({
1646
+ workflowName,
1647
+ runId,
1648
+ stepId,
1649
+ result,
1650
+ requestContext
1553
1651
  }) {
1554
- 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;
1555
1673
  }
1556
- updateWorkflowState({
1557
- // workflowName,
1558
- // runId,
1559
- // opts,
1674
+ async updateWorkflowState({
1675
+ workflowName,
1676
+ runId,
1677
+ opts
1560
1678
  }) {
1561
- 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;
1562
1689
  }
1563
1690
  async persistWorkflowSnapshot({
1564
1691
  workflowName,
1565
1692
  runId,
1566
1693
  resourceId,
1567
- snapshot
1694
+ snapshot,
1695
+ createdAt,
1696
+ updatedAt
1568
1697
  }) {
1569
1698
  try {
1570
1699
  const table = await this.client.openTable(storage.TABLE_WORKFLOW_SNAPSHOT);
1571
- 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)}'`);
1572
1701
  const records = await query.toArray();
1573
- let createdAt;
1574
- const now = Date.now();
1702
+ let createdAtValue;
1703
+ const now = createdAt?.getTime() ?? Date.now();
1575
1704
  if (records.length > 0) {
1576
- createdAt = records[0].createdAt ?? now;
1705
+ createdAtValue = records[0].createdAt ?? now;
1577
1706
  } else {
1578
- createdAt = now;
1707
+ createdAtValue = now;
1579
1708
  }
1580
1709
  const { status, value, ...rest } = snapshot;
1581
1710
  const record = {
@@ -1584,8 +1713,8 @@ var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1584
1713
  resourceId,
1585
1714
  snapshot: JSON.stringify({ status, value, ...rest }),
1586
1715
  // this is to ensure status is always just before value, for when querying the db by status
1587
- createdAt,
1588
- updatedAt: now
1716
+ createdAt: createdAtValue,
1717
+ updatedAt: updatedAt ?? now
1589
1718
  };
1590
1719
  await table.mergeInsert(["workflow_name", "run_id"]).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute([record]);
1591
1720
  } catch (error$1) {
@@ -1606,7 +1735,7 @@ var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1606
1735
  }) {
1607
1736
  try {
1608
1737
  const table = await this.client.openTable(storage.TABLE_WORKFLOW_SNAPSHOT);
1609
- 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)}'`);
1610
1739
  const records = await query.toArray();
1611
1740
  return records.length > 0 ? JSON.parse(records[0].snapshot) : null;
1612
1741
  } catch (error$1) {
@@ -1624,9 +1753,9 @@ var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1624
1753
  async getWorkflowRunById(args) {
1625
1754
  try {
1626
1755
  const table = await this.client.openTable(storage.TABLE_WORKFLOW_SNAPSHOT);
1627
- let whereClause = `run_id = '${args.runId}'`;
1756
+ let whereClause = `run_id = '${escapeSql(args.runId)}'`;
1628
1757
  if (args.workflowName) {
1629
- whereClause += ` AND workflow_name = '${args.workflowName}'`;
1758
+ whereClause += ` AND workflow_name = '${escapeSql(args.workflowName)}'`;
1630
1759
  }
1631
1760
  const query = table.query().where(whereClause);
1632
1761
  const records = await query.toArray();
@@ -1648,7 +1777,7 @@ var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1648
1777
  async deleteWorkflowRunById({ runId, workflowName }) {
1649
1778
  try {
1650
1779
  const table = await this.client.openTable(storage.TABLE_WORKFLOW_SNAPSHOT);
1651
- const whereClause = `run_id = '${runId.replace(/'/g, "''")}' AND workflow_name = '${workflowName.replace(/'/g, "''")}'`;
1780
+ const whereClause = `run_id = '${escapeSql(runId)}' AND workflow_name = '${escapeSql(workflowName)}'`;
1652
1781
  await table.delete(whereClause);
1653
1782
  } catch (error$1) {
1654
1783
  throw new error.MastraError(
@@ -1668,14 +1797,14 @@ var StoreWorkflowsLance = class extends storage.WorkflowsStorage {
1668
1797
  let query = table.query();
1669
1798
  const conditions = [];
1670
1799
  if (args?.workflowName) {
1671
- conditions.push(`workflow_name = '${args.workflowName.replace(/'/g, "''")}'`);
1800
+ conditions.push(`workflow_name = '${escapeSql(args.workflowName)}'`);
1672
1801
  }
1673
1802
  if (args?.status) {
1674
1803
  const escapedStatus = args.status.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/%/g, "\\%").replace(/_/g, "\\_");
1675
1804
  conditions.push(`\`snapshot\` LIKE '%"status":"${escapedStatus}","value"%'`);
1676
1805
  }
1677
1806
  if (args?.resourceId) {
1678
- conditions.push(`\`resourceId\` = '${args.resourceId}'`);
1807
+ conditions.push(`\`resourceId\` = '${escapeSql(args.resourceId)}'`);
1679
1808
  }
1680
1809
  if (args?.fromDate instanceof Date) {
1681
1810
  conditions.push(`\`createdAt\` >= ${args.fromDate.getTime()}`);
@@ -1764,12 +1893,10 @@ var LanceStorage = class _LanceStorage extends storage.MastraStorage {
1764
1893
  const instance = new _LanceStorage(id, name, storageOptions?.disableInit);
1765
1894
  try {
1766
1895
  instance.lanceClient = await lancedb.connect(uri, connectionOptions);
1767
- const operations = new StoreOperationsLance({ client: instance.lanceClient });
1768
1896
  instance.stores = {
1769
- operations: new StoreOperationsLance({ client: instance.lanceClient }),
1770
1897
  workflows: new StoreWorkflowsLance({ client: instance.lanceClient }),
1771
1898
  scores: new StoreScoresLance({ client: instance.lanceClient }),
1772
- memory: new StoreMemoryLance({ client: instance.lanceClient, operations })
1899
+ memory: new StoreMemoryLance({ client: instance.lanceClient })
1773
1900
  };
1774
1901
  return instance;
1775
1902
  } catch (e) {
@@ -1786,67 +1913,43 @@ var LanceStorage = class _LanceStorage extends storage.MastraStorage {
1786
1913
  }
1787
1914
  }
1788
1915
  /**
1789
- * @internal
1790
- * Private constructor to enforce using the create factory method
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
+ * ```
1791
1934
  */
1792
- constructor(id, name, disableInit) {
1793
- super({ id, name, disableInit });
1794
- const operations = new StoreOperationsLance({ client: this.lanceClient });
1795
- this.stores = {
1796
- operations: new StoreOperationsLance({ client: this.lanceClient }),
1797
- workflows: new StoreWorkflowsLance({ client: this.lanceClient }),
1798
- scores: new StoreScoresLance({ client: this.lanceClient }),
1799
- memory: new StoreMemoryLance({ client: this.lanceClient, operations })
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 })
1800
1942
  };
1801
- }
1802
- async createTable({
1803
- tableName,
1804
- schema
1805
- }) {
1806
- return this.stores.operations.createTable({ tableName, schema });
1807
- }
1808
- async dropTable({ tableName }) {
1809
- return this.stores.operations.dropTable({ tableName });
1810
- }
1811
- async alterTable({
1812
- tableName,
1813
- schema,
1814
- ifNotExists
1815
- }) {
1816
- return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
1817
- }
1818
- async clearTable({ tableName }) {
1819
- return this.stores.operations.clearTable({ tableName });
1820
- }
1821
- async insert({ tableName, record }) {
1822
- return this.stores.operations.insert({ tableName, record });
1823
- }
1824
- async batchInsert({ tableName, records }) {
1825
- return this.stores.operations.batchInsert({ tableName, records });
1826
- }
1827
- async load({ tableName, keys }) {
1828
- return this.stores.operations.load({ tableName, keys });
1829
- }
1830
- async getThreadById({ threadId }) {
1831
- return this.stores.memory.getThreadById({ threadId });
1943
+ return instance;
1832
1944
  }
1833
1945
  /**
1834
- * Saves a thread to the database. This function doesn't overwrite existing threads.
1835
- * @param thread - The thread to save
1836
- * @returns The saved thread
1946
+ * @internal
1947
+ * Private constructor to enforce using the create factory method.
1948
+ * Note: stores is initialized in create() after the lanceClient is connected.
1837
1949
  */
1838
- async saveThread({ thread }) {
1839
- return this.stores.memory.saveThread({ thread });
1840
- }
1841
- async updateThread({
1842
- id,
1843
- title,
1844
- metadata
1845
- }) {
1846
- return this.stores.memory.updateThread({ id, title, metadata });
1847
- }
1848
- async deleteThread({ threadId }) {
1849
- return this.stores.memory.deleteThread({ threadId });
1950
+ constructor(id, name, disableInit) {
1951
+ super({ id, name, disableInit });
1952
+ this.stores = {};
1850
1953
  }
1851
1954
  get supports() {
1852
1955
  return {
@@ -1854,154 +1957,13 @@ var LanceStorage = class _LanceStorage extends storage.MastraStorage {
1854
1957
  resourceWorkingMemory: true,
1855
1958
  hasColumn: true,
1856
1959
  createTable: true,
1857
- deleteMessages: false,
1858
- listScoresBySpan: true
1960
+ deleteMessages: true,
1961
+ observability: false,
1962
+ indexManagement: false,
1963
+ listScoresBySpan: true,
1964
+ agents: false
1859
1965
  };
1860
1966
  }
1861
- async getResourceById({ resourceId }) {
1862
- return this.stores.memory.getResourceById({ resourceId });
1863
- }
1864
- async saveResource({ resource }) {
1865
- return this.stores.memory.saveResource({ resource });
1866
- }
1867
- async updateResource({
1868
- resourceId,
1869
- workingMemory,
1870
- metadata
1871
- }) {
1872
- return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
1873
- }
1874
- /**
1875
- * Processes messages to include context messages based on withPreviousMessages and withNextMessages
1876
- * @param records - The sorted array of records to process
1877
- * @param include - The array of include specifications with context parameters
1878
- * @returns The processed array with context messages included
1879
- */
1880
- processMessagesWithContext(records, include) {
1881
- const messagesWithContext = include.filter((item) => item.withPreviousMessages || item.withNextMessages);
1882
- if (messagesWithContext.length === 0) {
1883
- return records;
1884
- }
1885
- const messageIndexMap = /* @__PURE__ */ new Map();
1886
- records.forEach((message, index) => {
1887
- messageIndexMap.set(message.id, index);
1888
- });
1889
- const additionalIndices = /* @__PURE__ */ new Set();
1890
- for (const item of messagesWithContext) {
1891
- const messageIndex = messageIndexMap.get(item.id);
1892
- if (messageIndex !== void 0) {
1893
- if (item.withPreviousMessages) {
1894
- const startIdx = Math.max(0, messageIndex - item.withPreviousMessages);
1895
- for (let i = startIdx; i < messageIndex; i++) {
1896
- additionalIndices.add(i);
1897
- }
1898
- }
1899
- if (item.withNextMessages) {
1900
- const endIdx = Math.min(records.length - 1, messageIndex + item.withNextMessages);
1901
- for (let i = messageIndex + 1; i <= endIdx; i++) {
1902
- additionalIndices.add(i);
1903
- }
1904
- }
1905
- }
1906
- }
1907
- if (additionalIndices.size === 0) {
1908
- return records;
1909
- }
1910
- const originalMatchIds = new Set(include.map((item) => item.id));
1911
- const allIndices = /* @__PURE__ */ new Set();
1912
- records.forEach((record, index) => {
1913
- if (originalMatchIds.has(record.id)) {
1914
- allIndices.add(index);
1915
- }
1916
- });
1917
- additionalIndices.forEach((index) => {
1918
- allIndices.add(index);
1919
- });
1920
- return Array.from(allIndices).sort((a, b) => a - b).map((index) => records[index]);
1921
- }
1922
- async listMessagesById({ messageIds }) {
1923
- return this.stores.memory.listMessagesById({ messageIds });
1924
- }
1925
- async saveMessages(args) {
1926
- return this.stores.memory.saveMessages(args);
1927
- }
1928
- async updateMessages(_args) {
1929
- return this.stores.memory.updateMessages(_args);
1930
- }
1931
- async listWorkflowRuns(args) {
1932
- return this.stores.workflows.listWorkflowRuns(args);
1933
- }
1934
- async getWorkflowRunById(args) {
1935
- return this.stores.workflows.getWorkflowRunById(args);
1936
- }
1937
- async deleteWorkflowRunById({ runId, workflowName }) {
1938
- return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
1939
- }
1940
- async updateWorkflowResults({
1941
- workflowName,
1942
- runId,
1943
- stepId,
1944
- result,
1945
- requestContext
1946
- }) {
1947
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
1948
- }
1949
- async updateWorkflowState({
1950
- workflowName,
1951
- runId,
1952
- opts
1953
- }) {
1954
- return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
1955
- }
1956
- async persistWorkflowSnapshot({
1957
- workflowName,
1958
- runId,
1959
- resourceId,
1960
- snapshot
1961
- }) {
1962
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
1963
- }
1964
- async loadWorkflowSnapshot({
1965
- workflowName,
1966
- runId
1967
- }) {
1968
- return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
1969
- }
1970
- async getScoreById({ id: _id }) {
1971
- return this.stores.scores.getScoreById({ id: _id });
1972
- }
1973
- async listScoresByScorerId({
1974
- scorerId,
1975
- source,
1976
- entityId,
1977
- entityType,
1978
- pagination
1979
- }) {
1980
- return this.stores.scores.listScoresByScorerId({ scorerId, source, pagination, entityId, entityType });
1981
- }
1982
- async saveScore(score) {
1983
- return this.stores.scores.saveScore(score);
1984
- }
1985
- async listScoresByRunId({
1986
- runId,
1987
- pagination
1988
- }) {
1989
- return this.stores.scores.listScoresByRunId({ runId, pagination });
1990
- }
1991
- async listScoresByEntityId({
1992
- entityId,
1993
- entityType,
1994
- pagination
1995
- }) {
1996
- return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
1997
- }
1998
- async listScoresBySpan({
1999
- traceId,
2000
- spanId,
2001
- pagination
2002
- }) {
2003
- return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
2004
- }
2005
1967
  };
2006
1968
  var LanceFilterTranslator = class extends filter.BaseFilterTranslator {
2007
1969
  translate(filter) {