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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { connect, Index } from '@lancedb/lancedb';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { MemoryStorage, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, createStorageErrorId, normalizePerPage, calculatePagination, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, createVectorErrorId, getDefaultValue, ensureDate } from '@mastra/core/storage';
3
+ import { MemoryStorage, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, createStorageErrorId, normalizePerPage, calculatePagination, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, WorkflowsStorage, ensureDate, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, createVectorErrorId, getDefaultValue } from '@mastra/core/storage';
4
4
  import { MessageList } from '@mastra/core/agent';
5
5
  import { MastraBase } from '@mastra/core/base';
6
6
  import { Utf8, Float64, Binary, Float32, Int32, Field, Schema } from 'apache-arrow';
@@ -950,27 +950,63 @@ var StoreMemoryLance = class extends MemoryStorage {
950
950
  );
951
951
  }
952
952
  }
953
- async listThreadsByResourceId(args) {
953
+ async listThreads(args) {
954
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
955
+ try {
956
+ this.validatePaginationInput(page, perPageInput ?? 100);
957
+ } catch (error) {
958
+ throw new MastraError(
959
+ {
960
+ id: createStorageErrorId("LANCE", "LIST_THREADS", "INVALID_PAGE"),
961
+ domain: ErrorDomain.STORAGE,
962
+ category: ErrorCategory.USER,
963
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
964
+ },
965
+ error instanceof Error ? error : new Error("Invalid pagination parameters")
966
+ );
967
+ }
968
+ const perPage = normalizePerPage(perPageInput, 100);
969
+ try {
970
+ this.validateMetadataKeys(filter?.metadata);
971
+ } catch (error) {
972
+ throw new MastraError(
973
+ {
974
+ id: createStorageErrorId("LANCE", "LIST_THREADS", "INVALID_METADATA_KEY"),
975
+ domain: ErrorDomain.STORAGE,
976
+ category: ErrorCategory.USER,
977
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
978
+ },
979
+ error instanceof Error ? error : new Error("Invalid metadata key")
980
+ );
981
+ }
954
982
  try {
955
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
956
- const perPage = normalizePerPage(perPageInput, 100);
957
- if (page < 0) {
958
- throw new MastraError(
959
- {
960
- id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
961
- domain: ErrorDomain.STORAGE,
962
- category: ErrorCategory.USER,
963
- details: { page }
964
- },
965
- new Error("page must be >= 0")
966
- );
967
- }
968
983
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
969
984
  const { field, direction } = this.parseOrderBy(orderBy);
970
985
  const table = await this.client.openTable(TABLE_THREADS);
971
- const total = await table.countRows(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
972
- const query = table.query().where(`\`resourceId\` = '${this.escapeSql(resourceId)}'`);
973
- const records = await query.toArray();
986
+ const whereClauses = [];
987
+ if (filter?.resourceId) {
988
+ whereClauses.push(`\`resourceId\` = '${this.escapeSql(filter.resourceId)}'`);
989
+ }
990
+ const whereClause = whereClauses.length > 0 ? whereClauses.join(" AND ") : "";
991
+ const query = whereClause ? table.query().where(whereClause) : table.query();
992
+ let records = await query.toArray();
993
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
994
+ records = records.filter((record) => {
995
+ if (!record.metadata) return false;
996
+ let recordMeta;
997
+ if (typeof record.metadata === "string") {
998
+ try {
999
+ recordMeta = JSON.parse(record.metadata);
1000
+ } catch {
1001
+ return false;
1002
+ }
1003
+ } else {
1004
+ recordMeta = record.metadata;
1005
+ }
1006
+ return Object.entries(filter.metadata).every(([key, value]) => recordMeta[key] === value);
1007
+ });
1008
+ }
1009
+ const total = records.length;
974
1010
  records.sort((a, b) => {
975
1011
  const aValue = ["createdAt", "updatedAt"].includes(field) ? new Date(a[field]).getTime() : a[field];
976
1012
  const bValue = ["createdAt", "updatedAt"].includes(field) ? new Date(b[field]).getTime() : b[field];
@@ -997,7 +1033,7 @@ var StoreMemoryLance = class extends MemoryStorage {
997
1033
  } catch (error) {
998
1034
  throw new MastraError(
999
1035
  {
1000
- id: createStorageErrorId("LANCE", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1036
+ id: createStorageErrorId("LANCE", "LIST_THREADS", "FAILED"),
1001
1037
  domain: ErrorDomain.STORAGE,
1002
1038
  category: ErrorCategory.THIRD_PARTY
1003
1039
  },
@@ -1603,24 +1639,6 @@ var StoreScoresLance = class extends ScoresStorage {
1603
1639
  function escapeSql(str) {
1604
1640
  return str.replace(/'/g, "''");
1605
1641
  }
1606
- function parseWorkflowRun(row) {
1607
- let parsedSnapshot = row.snapshot;
1608
- if (typeof parsedSnapshot === "string") {
1609
- try {
1610
- parsedSnapshot = JSON.parse(row.snapshot);
1611
- } catch (e) {
1612
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1613
- }
1614
- }
1615
- return {
1616
- workflowName: row.workflow_name,
1617
- runId: row.run_id,
1618
- snapshot: parsedSnapshot,
1619
- createdAt: ensureDate(row.createdAt),
1620
- updatedAt: ensureDate(row.updatedAt),
1621
- resourceId: row.resourceId
1622
- };
1623
- }
1624
1642
  var StoreWorkflowsLance = class extends WorkflowsStorage {
1625
1643
  client;
1626
1644
  #db;
@@ -1630,6 +1648,24 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1630
1648
  this.client = client;
1631
1649
  this.#db = new LanceDB({ client });
1632
1650
  }
1651
+ parseWorkflowRun(row) {
1652
+ let parsedSnapshot = row.snapshot;
1653
+ if (typeof parsedSnapshot === "string") {
1654
+ try {
1655
+ parsedSnapshot = JSON.parse(row.snapshot);
1656
+ } catch (e) {
1657
+ this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1658
+ }
1659
+ }
1660
+ return {
1661
+ workflowName: row.workflow_name,
1662
+ runId: row.run_id,
1663
+ snapshot: parsedSnapshot,
1664
+ createdAt: ensureDate(row.createdAt),
1665
+ updatedAt: ensureDate(row.updatedAt),
1666
+ resourceId: row.resourceId
1667
+ };
1668
+ }
1633
1669
  async init() {
1634
1670
  const schema = TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT];
1635
1671
  await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema });
@@ -1761,7 +1797,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1761
1797
  const records = await query.toArray();
1762
1798
  if (records.length === 0) return null;
1763
1799
  const record = records[0];
1764
- return parseWorkflowRun(record);
1800
+ return this.parseWorkflowRun(record);
1765
1801
  } catch (error) {
1766
1802
  throw new MastraError(
1767
1803
  {
@@ -1838,7 +1874,7 @@ var StoreWorkflowsLance = class extends WorkflowsStorage {
1838
1874
  }
1839
1875
  const records = await query.toArray();
1840
1876
  return {
1841
- runs: records.map((record) => parseWorkflowRun(record)),
1877
+ runs: records.map((record) => this.parseWorkflowRun(record)),
1842
1878
  total: total || records.length
1843
1879
  };
1844
1880
  } catch (error) {
@@ -2328,6 +2364,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2328
2364
  }
2329
2365
  async query({
2330
2366
  tableName,
2367
+ indexName,
2331
2368
  queryVector,
2332
2369
  filter,
2333
2370
  includeVector = false,
@@ -2335,12 +2372,13 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2335
2372
  columns = [],
2336
2373
  includeAllColumns = false
2337
2374
  }) {
2375
+ const resolvedTableName = tableName ?? indexName;
2338
2376
  try {
2339
2377
  if (!this.lanceClient) {
2340
2378
  throw new Error("LanceDB client not initialized. Use LanceVectorStore.create() to create an instance");
2341
2379
  }
2342
- if (!tableName) {
2343
- throw new Error("tableName is required");
2380
+ if (!resolvedTableName) {
2381
+ throw new Error("tableName or indexName is required");
2344
2382
  }
2345
2383
  if (!queryVector) {
2346
2384
  throw new Error("queryVector is required");
@@ -2351,25 +2389,30 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2351
2389
  id: createVectorErrorId("LANCE", "QUERY", "INVALID_ARGS"),
2352
2390
  domain: ErrorDomain.STORAGE,
2353
2391
  category: ErrorCategory.USER,
2354
- text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
2355
- details: { tableName }
2392
+ text: error instanceof Error ? error.message : "Invalid query arguments",
2393
+ details: { tableName: resolvedTableName }
2356
2394
  },
2357
2395
  error
2358
2396
  );
2359
2397
  }
2360
2398
  try {
2361
- const table = await this.lanceClient.openTable(tableName);
2362
- const selectColumns = [...columns];
2363
- if (!selectColumns.includes("id")) {
2364
- selectColumns.push("id");
2399
+ const tables = await this.lanceClient.tableNames();
2400
+ if (!tables.includes(resolvedTableName)) {
2401
+ this.logger.debug(`Table ${resolvedTableName} does not exist. Returning empty results.`);
2402
+ return [];
2365
2403
  }
2404
+ const table = await this.lanceClient.openTable(resolvedTableName);
2366
2405
  let query = table.search(queryVector);
2367
2406
  if (filter && Object.keys(filter).length > 0) {
2368
2407
  const whereClause = this.filterTranslator(filter);
2369
2408
  this.logger.debug(`Where clause generated: ${whereClause}`);
2370
2409
  query = query.where(whereClause);
2371
2410
  }
2372
- if (!includeAllColumns && selectColumns.length > 0) {
2411
+ if (!includeAllColumns && columns.length > 0) {
2412
+ const selectColumns = [...columns];
2413
+ if (!selectColumns.includes("id")) {
2414
+ selectColumns.push("id");
2415
+ }
2373
2416
  query = query.select(selectColumns);
2374
2417
  }
2375
2418
  query = query.limit(topK);
@@ -2399,7 +2442,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2399
2442
  id: createVectorErrorId("LANCE", "QUERY", "FAILED"),
2400
2443
  domain: ErrorDomain.STORAGE,
2401
2444
  category: ErrorCategory.THIRD_PARTY,
2402
- details: { tableName, includeVector, columnsCount: columns?.length, includeAllColumns }
2445
+ details: { tableName: resolvedTableName, includeVector, columnsCount: columns?.length, includeAllColumns }
2403
2446
  },
2404
2447
  error
2405
2448
  );
@@ -2434,13 +2477,14 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2434
2477
  const translator = new LanceFilterTranslator();
2435
2478
  return translator.translate(prefixedFilter);
2436
2479
  }
2437
- async upsert({ tableName, vectors, metadata = [], ids = [] }) {
2480
+ async upsert({ tableName, indexName, vectors, metadata = [], ids = [] }) {
2481
+ const resolvedTableName = tableName ?? indexName;
2438
2482
  try {
2439
2483
  if (!this.lanceClient) {
2440
2484
  throw new Error("LanceDB client not initialized. Use LanceVectorStore.create() to create an instance");
2441
2485
  }
2442
- if (!tableName) {
2443
- throw new Error("tableName is required");
2486
+ if (!resolvedTableName) {
2487
+ throw new Error("tableName or indexName is required");
2444
2488
  }
2445
2489
  if (!vectors || !Array.isArray(vectors) || vectors.length === 0) {
2446
2490
  throw new Error("vectors array is required and must not be empty");
@@ -2451,18 +2495,21 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2451
2495
  id: createVectorErrorId("LANCE", "UPSERT", "INVALID_ARGS"),
2452
2496
  domain: ErrorDomain.STORAGE,
2453
2497
  category: ErrorCategory.USER,
2454
- text: "LanceDB client not initialized. Use LanceVectorStore.create() to create an instance",
2455
- details: { tableName }
2498
+ text: error instanceof Error ? error.message : "Invalid upsert arguments",
2499
+ details: { tableName: resolvedTableName }
2456
2500
  },
2457
2501
  error
2458
2502
  );
2459
2503
  }
2460
2504
  try {
2461
2505
  const tables = await this.lanceClient.tableNames();
2462
- if (!tables.includes(tableName)) {
2463
- throw new Error(`Table ${tableName} does not exist`);
2506
+ const tableExists = tables.includes(resolvedTableName);
2507
+ let table = null;
2508
+ if (!tableExists) {
2509
+ this.logger.debug(`Table ${resolvedTableName} does not exist. Creating it with the first upsert data.`);
2510
+ } else {
2511
+ table = await this.lanceClient.openTable(resolvedTableName);
2464
2512
  }
2465
- const table = await this.lanceClient.openTable(tableName);
2466
2513
  const vectorIds = ids.length === vectors.length ? ids : vectors.map((_, i) => ids[i] || crypto.randomUUID());
2467
2514
  const data = vectors.map((vector, i) => {
2468
2515
  const id = String(vectorIds[i]);
@@ -2479,7 +2526,42 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2479
2526
  }
2480
2527
  return rowData;
2481
2528
  });
2482
- await table.add(data, { mode: "overwrite" });
2529
+ if (table !== null) {
2530
+ const rowCount = await table.countRows();
2531
+ const schema = await table.schema();
2532
+ const existingColumns = new Set(schema.fields.map((f) => f.name));
2533
+ const dataColumns = new Set(Object.keys(data[0] || {}));
2534
+ const extraColumns = [...dataColumns].filter((col) => !existingColumns.has(col));
2535
+ const missingSchemaColumns = [...existingColumns].filter((col) => !dataColumns.has(col));
2536
+ const hasSchemaMismatch = extraColumns.length > 0 || missingSchemaColumns.length > 0;
2537
+ if (rowCount === 0 && extraColumns.length > 0) {
2538
+ this.logger.warn(
2539
+ `Table ${resolvedTableName} is empty and data has extra columns ${extraColumns.join(", ")}. Recreating with new schema.`
2540
+ );
2541
+ await this.lanceClient.dropTable(resolvedTableName);
2542
+ await this.lanceClient.createTable(resolvedTableName, data);
2543
+ } else if (hasSchemaMismatch) {
2544
+ if (extraColumns.length > 0) {
2545
+ this.logger.warn(
2546
+ `Table ${resolvedTableName} has ${rowCount} rows. Columns ${extraColumns.join(", ")} will be dropped from upsert.`
2547
+ );
2548
+ }
2549
+ const schemaFieldNames = schema.fields.map((f) => f.name);
2550
+ const normalizedData = data.map((row) => {
2551
+ const normalized = {};
2552
+ for (const col of schemaFieldNames) {
2553
+ normalized[col] = col in row ? row[col] : null;
2554
+ }
2555
+ return normalized;
2556
+ });
2557
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(normalizedData);
2558
+ } else {
2559
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(data);
2560
+ }
2561
+ } else {
2562
+ this.logger.debug(`Creating table ${resolvedTableName} with initial data`);
2563
+ await this.lanceClient.createTable(resolvedTableName, data);
2564
+ }
2483
2565
  return vectorIds;
2484
2566
  } catch (error) {
2485
2567
  throw new MastraError(
@@ -2487,7 +2569,12 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2487
2569
  id: createVectorErrorId("LANCE", "UPSERT", "FAILED"),
2488
2570
  domain: ErrorDomain.STORAGE,
2489
2571
  category: ErrorCategory.THIRD_PARTY,
2490
- details: { tableName, vectorCount: vectors.length, metadataCount: metadata.length, idsCount: ids.length }
2572
+ details: {
2573
+ tableName: resolvedTableName,
2574
+ vectorCount: vectors.length,
2575
+ metadataCount: metadata.length,
2576
+ idsCount: ids.length
2577
+ }
2491
2578
  },
2492
2579
  error
2493
2580
  );
@@ -2584,7 +2671,17 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2584
2671
  }
2585
2672
  }
2586
2673
  /**
2587
- * indexName is actually a column name in a table in lanceDB
2674
+ * Creates a vector index on a table.
2675
+ *
2676
+ * The behavior of `indexName` depends on whether `tableName` is provided:
2677
+ * - With `tableName`: `indexName` is the column to index (advanced use case)
2678
+ * - Without `tableName`: `indexName` becomes the table name, and 'vector' is used as the column (Memory compatibility)
2679
+ *
2680
+ * @param tableName - Optional table name. If not provided, defaults to indexName.
2681
+ * @param indexName - The index/column name, or table name if tableName is not provided.
2682
+ * @param dimension - Vector dimension size.
2683
+ * @param metric - Distance metric: 'cosine', 'euclidean', or 'dotproduct'.
2684
+ * @param indexConfig - Optional index configuration.
2588
2685
  */
2589
2686
  async createIndex({
2590
2687
  tableName,
@@ -2593,13 +2690,12 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2593
2690
  metric = "cosine",
2594
2691
  indexConfig = {}
2595
2692
  }) {
2693
+ const resolvedTableName = tableName ?? indexName;
2694
+ const columnToIndex = tableName ? indexName : "vector";
2596
2695
  try {
2597
2696
  if (!this.lanceClient) {
2598
2697
  throw new Error("LanceDB client not initialized. Use LanceVectorStore.create() to create an instance");
2599
2698
  }
2600
- if (!tableName) {
2601
- throw new Error("tableName is required");
2602
- }
2603
2699
  if (!indexName) {
2604
2700
  throw new Error("indexName is required");
2605
2701
  }
@@ -2612,19 +2708,33 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2612
2708
  id: createVectorErrorId("LANCE", "CREATE_INDEX", "INVALID_ARGS"),
2613
2709
  domain: ErrorDomain.STORAGE,
2614
2710
  category: ErrorCategory.USER,
2615
- details: { tableName: tableName || "", indexName, dimension, metric }
2711
+ details: { tableName: resolvedTableName, indexName, dimension, metric }
2616
2712
  },
2617
2713
  err
2618
2714
  );
2619
2715
  }
2620
2716
  try {
2621
2717
  const tables = await this.lanceClient.tableNames();
2622
- if (!tables.includes(tableName)) {
2623
- throw new Error(
2624
- `Table ${tableName} does not exist. Please create the table first by calling createTable() method.`
2718
+ let table;
2719
+ if (!tables.includes(resolvedTableName)) {
2720
+ this.logger.debug(
2721
+ `Table ${resolvedTableName} does not exist. Creating empty table with dimension ${dimension}.`
2625
2722
  );
2723
+ const initVector = new Array(dimension).fill(0);
2724
+ table = await this.lanceClient.createTable(resolvedTableName, [{ id: "__init__", vector: initVector }]);
2725
+ try {
2726
+ await table.delete("id = '__init__'");
2727
+ } catch (deleteError) {
2728
+ this.logger.warn(
2729
+ `Failed to delete initialization row from ${resolvedTableName}. Subsequent queries may include '__init__' row.`,
2730
+ deleteError
2731
+ );
2732
+ }
2733
+ this.logger.debug(`Table ${resolvedTableName} created. Index creation deferred until data is available.`);
2734
+ return;
2735
+ } else {
2736
+ table = await this.lanceClient.openTable(resolvedTableName);
2626
2737
  }
2627
- const table = await this.lanceClient.openTable(tableName);
2628
2738
  let metricType;
2629
2739
  if (metric === "euclidean") {
2630
2740
  metricType = "l2";
@@ -2633,8 +2743,15 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2633
2743
  } else if (metric === "cosine") {
2634
2744
  metricType = "cosine";
2635
2745
  }
2746
+ const rowCount = await table.countRows();
2747
+ if (rowCount < 256) {
2748
+ this.logger.warn(
2749
+ `Table ${resolvedTableName} has ${rowCount} rows, which is below the 256 row minimum for index creation. Skipping index creation.`
2750
+ );
2751
+ return;
2752
+ }
2636
2753
  if (indexConfig.type === "ivfflat") {
2637
- await table.createIndex(indexName, {
2754
+ await table.createIndex(columnToIndex, {
2638
2755
  config: Index.ivfPq({
2639
2756
  numPartitions: indexConfig.numPartitions || 128,
2640
2757
  numSubVectors: indexConfig.numSubVectors || 16,
@@ -2643,7 +2760,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2643
2760
  });
2644
2761
  } else {
2645
2762
  this.logger.debug("Creating HNSW PQ index with config:", indexConfig);
2646
- await table.createIndex(indexName, {
2763
+ await table.createIndex(columnToIndex, {
2647
2764
  config: Index.hnswPq({
2648
2765
  m: indexConfig?.hnsw?.m || 16,
2649
2766
  efConstruction: indexConfig?.hnsw?.efConstruction || 100,
@@ -2657,7 +2774,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2657
2774
  id: createVectorErrorId("LANCE", "CREATE_INDEX", "FAILED"),
2658
2775
  domain: ErrorDomain.STORAGE,
2659
2776
  category: ErrorCategory.THIRD_PARTY,
2660
- details: { tableName: tableName || "", indexName, dimension }
2777
+ details: { tableName: resolvedTableName, indexName, dimension }
2661
2778
  },
2662
2779
  error
2663
2780
  );
@@ -2955,7 +3072,7 @@ var LanceVectorStore = class _LanceVectorStore extends MastraVector {
2955
3072
  }
2956
3073
  return rowData;
2957
3074
  });
2958
- await table.add(updatedRecords, { mode: "overwrite" });
3075
+ await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(updatedRecords);
2959
3076
  return;
2960
3077
  }
2961
3078
  } catch (err) {