@mastra/upstash 1.0.0-beta.1 → 1.0.0-beta.3

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,4 +1,4 @@
1
- import { MastraStorage, StoreOperations, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, TABLE_RESOURCES, TABLE_MESSAGES, serializeDate } from '@mastra/core/storage';
1
+ import { MastraStorage, StoreOperations, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, TABLE_RESOURCES, TABLE_MESSAGES, serializeDate, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
2
2
  import { Redis } from '@upstash/redis';
3
3
  import { MessageList } from '@mastra/core/agent';
4
4
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
@@ -52,6 +52,9 @@ function getMessageKey(threadId, messageId) {
52
52
  const key = getKey(TABLE_MESSAGES, { threadId, id: messageId });
53
53
  return key;
54
54
  }
55
+ function getMessageIndexKey(messageId) {
56
+ return `msg-idx:${messageId}`;
57
+ }
55
58
  var StoreMemoryUpstash = class extends MemoryStorage {
56
59
  client;
57
60
  operations;
@@ -318,6 +321,7 @@ var StoreMemoryUpstash = class extends MemoryStorage {
318
321
  }
319
322
  }
320
323
  pipeline.set(key, message);
324
+ pipeline.set(getMessageIndexKey(message.id), message.threadId);
321
325
  pipeline.zadd(getThreadMessagesKey(message.threadId), {
322
326
  score,
323
327
  member: message.id
@@ -348,43 +352,60 @@ var StoreMemoryUpstash = class extends MemoryStorage {
348
352
  );
349
353
  }
350
354
  }
351
- async _getIncludedMessages(threadId, include) {
352
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
355
+ /**
356
+ * Lookup threadId for a message - tries index first (O(1)), falls back to scan (backwards compatible)
357
+ */
358
+ async _getThreadIdForMessage(messageId) {
359
+ const indexedThreadId = await this.client.get(getMessageIndexKey(messageId));
360
+ if (indexedThreadId) {
361
+ return indexedThreadId;
362
+ }
363
+ const existingKeyPattern = getMessageKey("*", messageId);
364
+ const keys = await this.operations.scanKeys(existingKeyPattern);
365
+ if (keys.length === 0) return null;
366
+ const messageData = await this.client.get(keys[0]);
367
+ if (!messageData) return null;
368
+ if (messageData.threadId) {
369
+ await this.client.set(getMessageIndexKey(messageId), messageData.threadId);
370
+ }
371
+ return messageData.threadId || null;
372
+ }
373
+ async _getIncludedMessages(include) {
374
+ if (!include?.length) return [];
353
375
  const messageIds = /* @__PURE__ */ new Set();
354
376
  const messageIdToThreadIds = {};
355
- if (include?.length) {
356
- for (const item of include) {
357
- messageIds.add(item.id);
358
- const itemThreadId = item.threadId || threadId;
359
- messageIdToThreadIds[item.id] = itemThreadId;
360
- const itemThreadMessagesKey = getThreadMessagesKey(itemThreadId);
361
- const rank = await this.client.zrank(itemThreadMessagesKey, item.id);
362
- if (rank === null) continue;
363
- if (item.withPreviousMessages) {
364
- const start = Math.max(0, rank - item.withPreviousMessages);
365
- const prevIds = rank === 0 ? [] : await this.client.zrange(itemThreadMessagesKey, start, rank - 1);
366
- prevIds.forEach((id) => {
367
- messageIds.add(id);
368
- messageIdToThreadIds[id] = itemThreadId;
369
- });
370
- }
371
- if (item.withNextMessages) {
372
- const nextIds = await this.client.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
373
- nextIds.forEach((id) => {
374
- messageIds.add(id);
375
- messageIdToThreadIds[id] = itemThreadId;
376
- });
377
- }
377
+ for (const item of include) {
378
+ const itemThreadId = await this._getThreadIdForMessage(item.id);
379
+ if (!itemThreadId) continue;
380
+ messageIds.add(item.id);
381
+ messageIdToThreadIds[item.id] = itemThreadId;
382
+ const itemThreadMessagesKey = getThreadMessagesKey(itemThreadId);
383
+ const rank = await this.client.zrank(itemThreadMessagesKey, item.id);
384
+ if (rank === null) continue;
385
+ if (item.withPreviousMessages) {
386
+ const start = Math.max(0, rank - item.withPreviousMessages);
387
+ const prevIds = rank === 0 ? [] : await this.client.zrange(itemThreadMessagesKey, start, rank - 1);
388
+ prevIds.forEach((id) => {
389
+ messageIds.add(id);
390
+ messageIdToThreadIds[id] = itemThreadId;
391
+ });
392
+ }
393
+ if (item.withNextMessages) {
394
+ const nextIds = await this.client.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
395
+ nextIds.forEach((id) => {
396
+ messageIds.add(id);
397
+ messageIdToThreadIds[id] = itemThreadId;
398
+ });
378
399
  }
379
- const pipeline = this.client.pipeline();
380
- Array.from(messageIds).forEach((id) => {
381
- const tId = messageIdToThreadIds[id] || threadId;
382
- pipeline.get(getMessageKey(tId, id));
383
- });
384
- const results = await pipeline.exec();
385
- return results.filter((result) => result !== null);
386
400
  }
387
- return [];
401
+ if (messageIds.size === 0) return [];
402
+ const pipeline = this.client.pipeline();
403
+ Array.from(messageIds).forEach((id) => {
404
+ const tId = messageIdToThreadIds[id];
405
+ pipeline.get(getMessageKey(tId, id));
406
+ });
407
+ const results = await pipeline.exec();
408
+ return results.filter((result) => result !== null);
388
409
  }
389
410
  parseStoredMessage(storedMessage) {
390
411
  const defaultMessageContent = { format: 2, parts: [{ type: "text", text: "" }] };
@@ -398,17 +419,49 @@ var StoreMemoryUpstash = class extends MemoryStorage {
398
419
  async listMessagesById({ messageIds }) {
399
420
  if (messageIds.length === 0) return { messages: [] };
400
421
  try {
401
- const threadKeys = await this.client.keys("thread:*");
402
- const result = await Promise.all(
403
- threadKeys.map((threadKey) => {
404
- const threadId = threadKey.split(":")[1];
405
- if (!threadId) throw new Error(`Failed to parse thread ID from thread key "${threadKey}"`);
406
- return this.client.mget(
407
- messageIds.map((id) => getMessageKey(threadId, id))
408
- );
409
- })
410
- );
411
- const rawMessages = result.flat(1).filter((msg) => !!msg);
422
+ const rawMessages = [];
423
+ const indexPipeline = this.client.pipeline();
424
+ messageIds.forEach((id) => indexPipeline.get(getMessageIndexKey(id)));
425
+ const indexResults = await indexPipeline.exec();
426
+ const indexedIds = [];
427
+ const unindexedIds = [];
428
+ messageIds.forEach((id, i) => {
429
+ const threadId = indexResults[i];
430
+ if (threadId) {
431
+ indexedIds.push({ messageId: id, threadId });
432
+ } else {
433
+ unindexedIds.push(id);
434
+ }
435
+ });
436
+ if (indexedIds.length > 0) {
437
+ const messagePipeline = this.client.pipeline();
438
+ indexedIds.forEach(({ messageId, threadId }) => messagePipeline.get(getMessageKey(threadId, messageId)));
439
+ const messageResults = await messagePipeline.exec();
440
+ rawMessages.push(...messageResults.filter((msg) => msg !== null));
441
+ }
442
+ if (unindexedIds.length > 0) {
443
+ const threadKeys = await this.client.keys("thread:*");
444
+ const result = await Promise.all(
445
+ threadKeys.map((threadKey) => {
446
+ const threadId = threadKey.split(":")[1];
447
+ if (!threadId) throw new Error(`Failed to parse thread ID from thread key "${threadKey}"`);
448
+ return this.client.mget(
449
+ unindexedIds.map((id) => getMessageKey(threadId, id))
450
+ );
451
+ })
452
+ );
453
+ const foundMessages = result.flat(1).filter((msg) => !!msg);
454
+ rawMessages.push(...foundMessages);
455
+ if (foundMessages.length > 0) {
456
+ const backfillPipeline = this.client.pipeline();
457
+ foundMessages.forEach((msg) => {
458
+ if (msg.threadId) {
459
+ backfillPipeline.set(getMessageIndexKey(msg.id), msg.threadId);
460
+ }
461
+ });
462
+ await backfillPipeline.exec();
463
+ }
464
+ }
412
465
  const list = new MessageList().add(rawMessages.map(this.parseStoredMessage), "memory");
413
466
  return { messages: list.get.all.db() };
414
467
  } catch (error) {
@@ -427,18 +480,18 @@ var StoreMemoryUpstash = class extends MemoryStorage {
427
480
  }
428
481
  async listMessages(args) {
429
482
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
430
- if (!threadId.trim()) {
483
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
484
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
431
485
  throw new MastraError(
432
486
  {
433
487
  id: "STORAGE_UPSTASH_LIST_MESSAGES_INVALID_THREAD_ID",
434
488
  domain: ErrorDomain.STORAGE,
435
489
  category: ErrorCategory.THIRD_PARTY,
436
- details: { threadId }
490
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
437
491
  },
438
- new Error("threadId must be a non-empty string")
492
+ new Error("threadId must be a non-empty string or array of non-empty strings")
439
493
  );
440
494
  }
441
- const threadMessagesKey = getThreadMessagesKey(threadId);
442
495
  const perPage = normalizePerPage(perPageInput, 40);
443
496
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
444
497
  try {
@@ -455,11 +508,18 @@ var StoreMemoryUpstash = class extends MemoryStorage {
455
508
  }
456
509
  let includedMessages = [];
457
510
  if (include && include.length > 0) {
458
- const included = await this._getIncludedMessages(threadId, include);
511
+ const included = await this._getIncludedMessages(include);
459
512
  includedMessages = included.map(this.parseStoredMessage);
460
513
  }
461
- const allMessageIds = await this.client.zrange(threadMessagesKey, 0, -1);
462
- if (allMessageIds.length === 0) {
514
+ const allMessageIdsWithThreads = [];
515
+ for (const tid of threadIds) {
516
+ const threadMessagesKey = getThreadMessagesKey(tid);
517
+ const messageIds2 = await this.client.zrange(threadMessagesKey, 0, -1);
518
+ for (const mid of messageIds2) {
519
+ allMessageIdsWithThreads.push({ threadId: tid, messageId: mid });
520
+ }
521
+ }
522
+ if (allMessageIdsWithThreads.length === 0) {
463
523
  return {
464
524
  messages: [],
465
525
  total: 0,
@@ -469,7 +529,7 @@ var StoreMemoryUpstash = class extends MemoryStorage {
469
529
  };
470
530
  }
471
531
  const pipeline = this.client.pipeline();
472
- allMessageIds.forEach((id) => pipeline.get(getMessageKey(threadId, id)));
532
+ allMessageIdsWithThreads.forEach(({ threadId: tid, messageId }) => pipeline.get(getMessageKey(tid, messageId)));
473
533
  const results = await pipeline.exec();
474
534
  let messagesData = results.filter((msg) => msg !== null).map(this.parseStoredMessage);
475
535
  if (resourceId) {
@@ -498,13 +558,11 @@ var StoreMemoryUpstash = class extends MemoryStorage {
498
558
  }
499
559
  return 0;
500
560
  };
501
- if (orderBy) {
502
- messagesData.sort((a, b) => {
503
- const aValue = getFieldValue(a);
504
- const bValue = getFieldValue(b);
505
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
506
- });
507
- }
561
+ messagesData.sort((a, b) => {
562
+ const aValue = getFieldValue(a);
563
+ const bValue = getFieldValue(b);
564
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
565
+ });
508
566
  const total = messagesData.length;
509
567
  const start = offset;
510
568
  const end = perPageInput === false ? total : start + perPage;
@@ -525,23 +583,11 @@ var StoreMemoryUpstash = class extends MemoryStorage {
525
583
  }
526
584
  const list = new MessageList().add(allMessages, "memory");
527
585
  let finalMessages = list.get.all.db();
528
- if (orderBy) {
529
- finalMessages = finalMessages.sort((a, b) => {
530
- const aValue = getFieldValue(a);
531
- const bValue = getFieldValue(b);
532
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
533
- });
534
- } else {
535
- const messageIdToPosition = /* @__PURE__ */ new Map();
536
- allMessageIds.forEach((id, index) => {
537
- messageIdToPosition.set(id, index);
538
- });
539
- finalMessages = finalMessages.sort((a, b) => {
540
- const aPos = messageIdToPosition.get(a.id) ?? Number.MAX_SAFE_INTEGER;
541
- const bPos = messageIdToPosition.get(b.id) ?? Number.MAX_SAFE_INTEGER;
542
- return aPos - bPos;
543
- });
544
- }
586
+ finalMessages = finalMessages.sort((a, b) => {
587
+ const aValue = getFieldValue(a);
588
+ const bValue = getFieldValue(b);
589
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
590
+ });
545
591
  const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
546
592
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
547
593
  const hasMore = perPageInput !== false && !allThreadMessagesReturned && end < total;
@@ -559,7 +605,7 @@ var StoreMemoryUpstash = class extends MemoryStorage {
559
605
  domain: ErrorDomain.STORAGE,
560
606
  category: ErrorCategory.THIRD_PARTY,
561
607
  details: {
562
- threadId,
608
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
563
609
  resourceId: resourceId ?? ""
564
610
  }
565
611
  },
@@ -764,13 +810,33 @@ var StoreMemoryUpstash = class extends MemoryStorage {
764
810
  try {
765
811
  const threadIds = /* @__PURE__ */ new Set();
766
812
  const messageKeys = [];
767
- for (const messageId of messageIds) {
813
+ const foundMessageIds = [];
814
+ const indexPipeline = this.client.pipeline();
815
+ messageIds.forEach((id) => indexPipeline.get(getMessageIndexKey(id)));
816
+ const indexResults = await indexPipeline.exec();
817
+ const indexedMessages = [];
818
+ const unindexedMessageIds = [];
819
+ messageIds.forEach((id, i) => {
820
+ const threadId = indexResults[i];
821
+ if (threadId) {
822
+ indexedMessages.push({ messageId: id, threadId });
823
+ } else {
824
+ unindexedMessageIds.push(id);
825
+ }
826
+ });
827
+ for (const { messageId, threadId } of indexedMessages) {
828
+ messageKeys.push(getMessageKey(threadId, messageId));
829
+ foundMessageIds.push(messageId);
830
+ threadIds.add(threadId);
831
+ }
832
+ for (const messageId of unindexedMessageIds) {
768
833
  const pattern = getMessageKey("*", messageId);
769
834
  const keys = await this.operations.scanKeys(pattern);
770
835
  for (const key of keys) {
771
836
  const message = await this.client.get(key);
772
837
  if (message && message.id === messageId) {
773
838
  messageKeys.push(key);
839
+ foundMessageIds.push(messageId);
774
840
  if (message.threadId) {
775
841
  threadIds.add(message.threadId);
776
842
  }
@@ -785,6 +851,9 @@ var StoreMemoryUpstash = class extends MemoryStorage {
785
851
  for (const key of messageKeys) {
786
852
  pipeline.del(key);
787
853
  }
854
+ for (const messageId of foundMessageIds) {
855
+ pipeline.del(getMessageIndexKey(messageId));
856
+ }
788
857
  if (threadIds.size > 0) {
789
858
  for (const threadId of threadIds) {
790
859
  const threadKey = getKey(TABLE_THREADS, { id: threadId });
@@ -960,32 +1029,7 @@ var StoreOperationsUpstash = class extends StoreOperations {
960
1029
  }
961
1030
  };
962
1031
  function transformScoreRow(row) {
963
- const parseField = (v) => {
964
- if (typeof v === "string") {
965
- try {
966
- return JSON.parse(v);
967
- } catch {
968
- return v;
969
- }
970
- }
971
- return v;
972
- };
973
- return {
974
- ...row,
975
- scorer: parseField(row.scorer),
976
- preprocessStepResult: parseField(row.preprocessStepResult),
977
- generateScorePrompt: row.generateScorePrompt,
978
- generateReasonPrompt: row.generateReasonPrompt,
979
- analyzeStepResult: parseField(row.analyzeStepResult),
980
- metadata: parseField(row.metadata),
981
- input: parseField(row.input),
982
- output: parseField(row.output),
983
- additionalContext: parseField(row.additionalContext),
984
- requestContext: parseField(row.requestContext),
985
- entity: parseField(row.entity),
986
- createdAt: row.createdAt,
987
- updatedAt: row.updatedAt
988
- };
1032
+ return transformScoreRow$1(row);
989
1033
  }
990
1034
  var ScoresUpstash = class extends ScoresStorage {
991
1035
  client;
@@ -1009,7 +1053,9 @@ var ScoresUpstash = class extends ScoresStorage {
1009
1053
  id: "STORAGE_UPSTASH_STORAGE_GET_SCORE_BY_ID_FAILED",
1010
1054
  domain: ErrorDomain.STORAGE,
1011
1055
  category: ErrorCategory.THIRD_PARTY,
1012
- details: { id }
1056
+ details: {
1057
+ ...id && { id }
1058
+ }
1013
1059
  },
1014
1060
  error
1015
1061
  );
@@ -1999,6 +2045,11 @@ var UpstashVector = class extends MastraVector {
1999
2045
  try {
2000
2046
  await this.client.deleteNamespace(namespace);
2001
2047
  } catch (error) {
2048
+ const errorMessage = error?.message || "";
2049
+ if (errorMessage.includes("does not exist") || errorMessage.includes("not found")) {
2050
+ this.logger.info(`Namespace ${namespace} does not exist, treating as already deleted`);
2051
+ return;
2052
+ }
2002
2053
  throw new MastraError(
2003
2054
  {
2004
2055
  id: "STORAGE_UPSTASH_VECTOR_DELETE_INDEX_FAILED",
@@ -2011,47 +2062,124 @@ var UpstashVector = class extends MastraVector {
2011
2062
  }
2012
2063
  }
2013
2064
  /**
2014
- * Updates a vector by its ID with the provided vector and/or metadata.
2015
- * @param indexName - The name of the namespace containing the vector.
2016
- * @param id - The ID of the vector to update.
2017
- * @param update - An object containing the vector and/or metadata to update.
2018
- * @param update.vector - An optional array of numbers representing the new vector.
2019
- * @param update.metadata - An optional record containing the new metadata.
2065
+ * Updates a vector by its ID or multiple vectors matching a filter.
2066
+ * @param params - Parameters containing the id or filter for targeting the vector(s) to update
2067
+ * @param params.indexName - The name of the namespace containing the vector.
2068
+ * @param params.id - The ID of the vector to update (mutually exclusive with filter).
2069
+ * @param params.filter - Filter to match multiple vectors to update (mutually exclusive with id).
2070
+ * @param params.update - An object containing the vector and/or metadata to update.
2020
2071
  * @returns A promise that resolves when the update is complete.
2021
2072
  * @throws Will throw an error if no updates are provided or if the update operation fails.
2022
2073
  */
2023
- async updateVector({ indexName: namespace, id, update }) {
2024
- if (!update.vector && !update.metadata && !update.sparseVector) {
2074
+ async updateVector(params) {
2075
+ const { indexName: namespace, update } = params;
2076
+ const upstashUpdate = update;
2077
+ const sparseVector = upstashUpdate.sparseVector;
2078
+ if ("id" in params && params.id && "filter" in params && params.filter) {
2079
+ throw new MastraError({
2080
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_MUTUALLY_EXCLUSIVE",
2081
+ text: "Cannot specify both id and filter - they are mutually exclusive",
2082
+ domain: ErrorDomain.STORAGE,
2083
+ category: ErrorCategory.USER,
2084
+ details: { namespace }
2085
+ });
2086
+ }
2087
+ if (!("id" in params && params.id) && !("filter" in params && params.filter)) {
2025
2088
  throw new MastraError({
2026
- id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2089
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_NO_TARGET",
2090
+ text: "Either id or filter must be provided",
2027
2091
  domain: ErrorDomain.STORAGE,
2028
- category: ErrorCategory.THIRD_PARTY,
2029
- details: { namespace, id },
2030
- text: "No update data provided"
2092
+ category: ErrorCategory.USER,
2093
+ details: { namespace }
2031
2094
  });
2032
2095
  }
2033
- if (!update.vector && !update.sparseVector && update.metadata) {
2096
+ if (!update.vector && !update.metadata && !sparseVector) {
2034
2097
  throw new MastraError({
2035
- id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2098
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_NO_PAYLOAD",
2099
+ text: "No update data provided",
2036
2100
  domain: ErrorDomain.STORAGE,
2037
- category: ErrorCategory.THIRD_PARTY,
2038
- details: { namespace, id },
2039
- text: "Both vector and metadata must be provided for an update"
2101
+ category: ErrorCategory.USER,
2102
+ details: { namespace }
2103
+ });
2104
+ }
2105
+ if ("filter" in params && params.filter && Object.keys(params.filter).length === 0) {
2106
+ throw new MastraError({
2107
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_EMPTY_FILTER",
2108
+ text: "Filter cannot be an empty filter object",
2109
+ domain: ErrorDomain.STORAGE,
2110
+ category: ErrorCategory.USER,
2111
+ details: { namespace }
2040
2112
  });
2041
2113
  }
2042
2114
  try {
2043
- const points = { id };
2044
- if (update.vector) points.vector = update.vector;
2045
- if (update.metadata) points.metadata = update.metadata;
2046
- if (update.sparseVector) points.sparseVector = update.sparseVector;
2047
- await this.client.upsert(points, { namespace });
2115
+ const ns = this.client.namespace(namespace);
2116
+ if ("id" in params && params.id) {
2117
+ const points = { id: params.id };
2118
+ if (!update.vector || !update.metadata) {
2119
+ try {
2120
+ const existing = await ns.fetch([params.id], {
2121
+ includeVectors: true,
2122
+ includeMetadata: true
2123
+ });
2124
+ if (existing && existing.length > 0 && existing[0]) {
2125
+ if (!update.vector && existing[0]?.vector) {
2126
+ points.vector = existing[0].vector;
2127
+ }
2128
+ if (!update.metadata && existing[0]?.metadata) {
2129
+ points.metadata = existing[0].metadata;
2130
+ }
2131
+ }
2132
+ } catch (fetchError) {
2133
+ this.logger.warn(`Failed to fetch existing vector ${params.id} for partial update: ${fetchError}`);
2134
+ }
2135
+ }
2136
+ if (update.vector) points.vector = update.vector;
2137
+ if (update.metadata) points.metadata = update.metadata;
2138
+ if (sparseVector) points.sparseVector = sparseVector;
2139
+ await ns.upsert(points);
2140
+ } else if ("filter" in params && params.filter) {
2141
+ const filterString = this.transformFilter(params.filter);
2142
+ if (filterString) {
2143
+ const stats = await this.describeIndex({ indexName: namespace });
2144
+ const dummyVector = new Array(stats.dimension).fill(1 / Math.sqrt(stats.dimension));
2145
+ const needsVectors = !update.vector;
2146
+ const results = await ns.query({
2147
+ vector: dummyVector,
2148
+ topK: 1e3,
2149
+ // Upstash's max query limit
2150
+ filter: filterString,
2151
+ includeVectors: needsVectors,
2152
+ includeMetadata: needsVectors
2153
+ });
2154
+ for (const result of results) {
2155
+ const points = { id: `${result.id}` };
2156
+ if (update.vector) {
2157
+ points.vector = update.vector;
2158
+ } else if (result.vector) {
2159
+ points.vector = result.vector;
2160
+ }
2161
+ if (update.metadata) {
2162
+ points.metadata = update.metadata;
2163
+ } else if (result.metadata) {
2164
+ points.metadata = result.metadata;
2165
+ }
2166
+ if (sparseVector) points.sparseVector = sparseVector;
2167
+ await ns.upsert(points);
2168
+ }
2169
+ }
2170
+ }
2048
2171
  } catch (error) {
2172
+ if (error instanceof MastraError) throw error;
2049
2173
  throw new MastraError(
2050
2174
  {
2051
2175
  id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2052
2176
  domain: ErrorDomain.STORAGE,
2053
2177
  category: ErrorCategory.THIRD_PARTY,
2054
- details: { namespace, id }
2178
+ details: {
2179
+ namespace,
2180
+ ..."id" in params && params.id && { id: params.id },
2181
+ ..."filter" in params && params.filter && { filter: JSON.stringify(params.filter) }
2182
+ }
2055
2183
  },
2056
2184
  error
2057
2185
  );
@@ -2066,22 +2194,109 @@ var UpstashVector = class extends MastraVector {
2066
2194
  */
2067
2195
  async deleteVector({ indexName: namespace, id }) {
2068
2196
  try {
2069
- await this.client.delete(id, {
2070
- namespace
2071
- });
2197
+ const ns = this.client.namespace(namespace);
2198
+ await ns.delete(id);
2072
2199
  } catch (error) {
2073
2200
  const mastraError = new MastraError(
2074
2201
  {
2075
2202
  id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTOR_FAILED",
2076
2203
  domain: ErrorDomain.STORAGE,
2077
2204
  category: ErrorCategory.THIRD_PARTY,
2078
- details: { namespace, id }
2205
+ details: {
2206
+ namespace,
2207
+ ...id && { id }
2208
+ }
2079
2209
  },
2080
2210
  error
2081
2211
  );
2082
2212
  this.logger?.error(mastraError.toString());
2083
2213
  }
2084
2214
  }
2215
+ /**
2216
+ * Deletes multiple vectors by IDs or filter.
2217
+ * @param indexName - The name of the namespace containing the vectors.
2218
+ * @param ids - Array of vector IDs to delete (mutually exclusive with filter).
2219
+ * @param filter - Filter to match vectors to delete (mutually exclusive with ids).
2220
+ * @returns A promise that resolves when the deletion is complete.
2221
+ * @throws Will throw an error if both ids and filter are provided, or if neither is provided.
2222
+ */
2223
+ async deleteVectors({ indexName: namespace, filter, ids }) {
2224
+ if (ids && filter) {
2225
+ throw new MastraError({
2226
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTORS_MUTUALLY_EXCLUSIVE",
2227
+ text: "Cannot specify both ids and filter - they are mutually exclusive",
2228
+ domain: ErrorDomain.STORAGE,
2229
+ category: ErrorCategory.USER,
2230
+ details: { namespace }
2231
+ });
2232
+ }
2233
+ if (!ids && !filter) {
2234
+ throw new MastraError({
2235
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTORS_NO_TARGET",
2236
+ text: "Either filter or ids must be provided",
2237
+ domain: ErrorDomain.STORAGE,
2238
+ category: ErrorCategory.USER,
2239
+ details: { namespace }
2240
+ });
2241
+ }
2242
+ if (ids && ids.length === 0) {
2243
+ throw new MastraError({
2244
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTORS_EMPTY_IDS",
2245
+ text: "Cannot delete with empty ids array",
2246
+ domain: ErrorDomain.STORAGE,
2247
+ category: ErrorCategory.USER,
2248
+ details: { namespace }
2249
+ });
2250
+ }
2251
+ if (filter && Object.keys(filter).length === 0) {
2252
+ throw new MastraError({
2253
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTORS_EMPTY_FILTER",
2254
+ text: "Cannot delete with empty filter object",
2255
+ domain: ErrorDomain.STORAGE,
2256
+ category: ErrorCategory.USER,
2257
+ details: { namespace }
2258
+ });
2259
+ }
2260
+ try {
2261
+ const ns = this.client.namespace(namespace);
2262
+ if (ids) {
2263
+ await ns.delete(ids);
2264
+ } else if (filter) {
2265
+ const filterString = this.transformFilter(filter);
2266
+ if (filterString) {
2267
+ const stats = await this.describeIndex({ indexName: namespace });
2268
+ const dummyVector = new Array(stats.dimension).fill(1 / Math.sqrt(stats.dimension));
2269
+ const results = await ns.query({
2270
+ vector: dummyVector,
2271
+ topK: 1e3,
2272
+ // Upstash's max query limit
2273
+ filter: filterString,
2274
+ includeVectors: false,
2275
+ includeMetadata: false
2276
+ });
2277
+ const idsToDelete = results.map((r) => `${r.id}`);
2278
+ if (idsToDelete.length > 0) {
2279
+ await ns.delete(idsToDelete);
2280
+ }
2281
+ }
2282
+ }
2283
+ } catch (error) {
2284
+ if (error instanceof MastraError) throw error;
2285
+ throw new MastraError(
2286
+ {
2287
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTORS_FAILED",
2288
+ domain: ErrorDomain.STORAGE,
2289
+ category: ErrorCategory.THIRD_PARTY,
2290
+ details: {
2291
+ namespace,
2292
+ ...filter && { filter: JSON.stringify(filter) },
2293
+ ...ids && { idsCount: ids.length }
2294
+ }
2295
+ },
2296
+ error
2297
+ );
2298
+ }
2299
+ }
2085
2300
  };
2086
2301
 
2087
2302
  // src/vector/prompt.ts