@mastra/upstash 0.0.0-vnextWorkflows-20250422142014 → 0.0.0-workflow-deno-20250616130925

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.
@@ -1,7 +1,7 @@
1
1
  import type { QueryResult } from '@mastra/core/vector';
2
2
  import dotenv from 'dotenv';
3
3
 
4
- import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, afterEach } from 'vitest';
4
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
5
5
 
6
6
  import { UpstashVector } from './';
7
7
 
@@ -13,7 +13,7 @@ function waitUntilVectorsIndexed(vector: UpstashVector, indexName: string, expec
13
13
  let attempts = 0;
14
14
  const interval = setInterval(async () => {
15
15
  try {
16
- const stats = await vector.describeIndex(indexName);
16
+ const stats = await vector.describeIndex({ indexName });
17
17
  if (stats && stats.count >= expectedCount) {
18
18
  clearInterval(interval);
19
19
  resolve(true);
@@ -59,12 +59,12 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
59
59
 
60
60
  // Cleanup: delete test index
61
61
  try {
62
- await vectorStore.deleteIndex(testIndexName);
62
+ await vectorStore.deleteIndex({ indexName: testIndexName });
63
63
  } catch (error) {
64
64
  console.warn('Failed to delete test index:', error);
65
65
  }
66
66
  try {
67
- await vectorStore.deleteIndex(filterIndexName);
67
+ await vectorStore.deleteIndex({ indexName: filterIndexName });
68
68
  } catch (error) {
69
69
  console.warn('Failed to delete filter index:', error);
70
70
  }
@@ -122,7 +122,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
122
122
  const testIndexName = 'test-index';
123
123
 
124
124
  afterEach(async () => {
125
- await vectorStore.deleteIndex(testIndexName);
125
+ await vectorStore.deleteIndex({ indexName: testIndexName });
126
126
  });
127
127
 
128
128
  it('should update the vector by id', async () => {
@@ -140,7 +140,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
140
140
  metadata: newMetaData,
141
141
  };
142
142
 
143
- await vectorStore.updateIndexById(testIndexName, idToBeUpdated, update);
143
+ await vectorStore.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
144
144
 
145
145
  await waitUntilVectorsIndexed(vectorStore, testIndexName, 3);
146
146
 
@@ -167,7 +167,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
167
167
  metadata: newMetaData,
168
168
  };
169
169
 
170
- await expect(vectorStore.updateIndexById(testIndexName, 'id', update)).rejects.toThrow(
170
+ await expect(vectorStore.updateVector({ indexName: testIndexName, id: 'id', update })).rejects.toThrow(
171
171
  'Both vector and metadata must be provided for an update',
172
172
  );
173
173
  });
@@ -183,7 +183,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
183
183
  vector: newVector,
184
184
  };
185
185
 
186
- await vectorStore.updateIndexById(testIndexName, idToBeUpdated, update);
186
+ await vectorStore.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
187
187
 
188
188
  await waitUntilVectorsIndexed(vectorStore, testIndexName, 3);
189
189
 
@@ -198,7 +198,9 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
198
198
  }, 500000);
199
199
 
200
200
  it('should throw exception when no updates are given', async () => {
201
- await expect(vectorStore.updateIndexById(testIndexName, 'id', {})).rejects.toThrow('No update data provided');
201
+ await expect(vectorStore.updateVector({ indexName: testIndexName, id: 'id', update: {} })).rejects.toThrow(
202
+ 'No update data provided',
203
+ );
202
204
  });
203
205
  });
204
206
 
@@ -206,7 +208,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
206
208
  const testVectors = [createVector(0, 1.0), createVector(1, 1.0), createVector(2, 1.0)];
207
209
 
208
210
  afterEach(async () => {
209
- await vectorStore.deleteIndex(testIndexName);
211
+ await vectorStore.deleteIndex({ indexName: testIndexName });
210
212
  });
211
213
 
212
214
  it('should delete the vector by id', async () => {
@@ -214,7 +216,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
214
216
  expect(ids).toHaveLength(3);
215
217
  const idToBeDeleted = ids[0];
216
218
 
217
- await vectorStore.deleteIndexById(testIndexName, idToBeDeleted);
219
+ await vectorStore.deleteVector({ indexName: testIndexName, id: idToBeDeleted });
218
220
 
219
221
  const results: QueryResult[] = await vectorStore.query({
220
222
  indexName: testIndexName,
@@ -246,7 +248,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
246
248
  });
247
249
 
248
250
  it('should describe an index correctly', async () => {
249
- const stats = await vectorStore.describeIndex('mastra_default');
251
+ const stats = await vectorStore.describeIndex({ indexName: 'mastra_default' });
250
252
  expect(stats).toEqual({
251
253
  dimension: 1536,
252
254
  metric: 'cosine',
@@ -256,6 +258,15 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
256
258
  });
257
259
 
258
260
  describe('Error Handling', () => {
261
+ const testIndexName = 'test_index_error';
262
+ beforeAll(async () => {
263
+ await vectorStore.createIndex({ indexName: testIndexName, dimension: 3 });
264
+ });
265
+
266
+ afterAll(async () => {
267
+ await vectorStore.deleteIndex({ indexName: testIndexName });
268
+ });
269
+
259
270
  it('should handle invalid dimension vectors', async () => {
260
271
  await expect(
261
272
  vectorStore.upsert({ indexName: testIndexName, vectors: [[1.0, 0.0]] }), // Wrong dimensions
@@ -1191,97 +1202,4 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
1191
1202
  });
1192
1203
  });
1193
1204
  });
1194
- describe('Deprecation Warnings', () => {
1195
- const indexName = 'testdeprecationwarnings';
1196
-
1197
- const indexName2 = 'testdeprecationwarnings2';
1198
-
1199
- let warnSpy;
1200
-
1201
- afterAll(async () => {
1202
- await vectorStore.deleteIndex(indexName);
1203
- await vectorStore.deleteIndex(indexName2);
1204
- });
1205
-
1206
- beforeEach(async () => {
1207
- warnSpy = vi.spyOn(vectorStore['logger'], 'warn');
1208
- });
1209
-
1210
- afterEach(async () => {
1211
- warnSpy.mockRestore();
1212
- await vectorStore.deleteIndex(indexName2);
1213
- });
1214
-
1215
- const createVector = (primaryDimension: number, value: number = 1.0): number[] => {
1216
- const vector = new Array(VECTOR_DIMENSION).fill(0);
1217
- vector[primaryDimension] = value;
1218
- // Normalize the vector for cosine similarity
1219
- const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
1220
- return vector.map(val => val / magnitude);
1221
- };
1222
-
1223
- it('should show deprecation warning when using individual args for upsert', async () => {
1224
- await vectorStore.upsert(indexName, [createVector(0, 2)], [{ test: 'data' }]);
1225
-
1226
- expect(warnSpy).toHaveBeenCalledWith(
1227
- expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
1228
- );
1229
- });
1230
-
1231
- it('should show deprecation warning when using individual args for query', async () => {
1232
- await vectorStore.query(indexName, createVector(0, 2), 5);
1233
-
1234
- expect(warnSpy).toHaveBeenCalledWith(
1235
- expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
1236
- );
1237
- });
1238
-
1239
- it('should not show deprecation warning when using object param for query', async () => {
1240
- await vectorStore.query({
1241
- indexName,
1242
- queryVector: createVector(0, 2),
1243
- topK: 5,
1244
- });
1245
-
1246
- expect(warnSpy).not.toHaveBeenCalled();
1247
- });
1248
-
1249
- it('should not show deprecation warning when using object param for createIndex', async () => {
1250
- await vectorStore.createIndex({
1251
- indexName: indexName2,
1252
- dimension: 3,
1253
- metric: 'cosine',
1254
- });
1255
-
1256
- expect(warnSpy).not.toHaveBeenCalled();
1257
- });
1258
-
1259
- it('should not show deprecation warning when using object param for upsert', async () => {
1260
- await vectorStore.upsert({
1261
- indexName,
1262
- vectors: [createVector(0, 2)],
1263
- metadata: [{ test: 'data' }],
1264
- });
1265
-
1266
- expect(warnSpy).not.toHaveBeenCalled();
1267
- });
1268
-
1269
- it('should maintain backward compatibility with individual args', async () => {
1270
- // Query
1271
- const queryResults = await vectorStore.query(indexName, createVector(0, 2), 5);
1272
- expect(Array.isArray(queryResults)).toBe(true);
1273
-
1274
- // CreateIndex
1275
- await expect(vectorStore.createIndex(indexName2, 3, 'cosine')).resolves.not.toThrow();
1276
-
1277
- // Upsert
1278
- const upsertResults = await vectorStore.upsert({
1279
- indexName,
1280
- vectors: [createVector(0, 2)],
1281
- metadata: [{ test: 'data' }],
1282
- });
1283
- expect(Array.isArray(upsertResults)).toBe(true);
1284
- expect(upsertResults).toHaveLength(1);
1285
- });
1286
- });
1287
1205
  });
@@ -1,9 +1,13 @@
1
1
  import { MastraVector } from '@mastra/core/vector';
2
2
  import type {
3
3
  CreateIndexParams,
4
- ParamsToArgs,
4
+ DeleteIndexParams,
5
+ DeleteVectorParams,
6
+ DescribeIndexParams,
7
+ IndexStats,
5
8
  QueryResult,
6
9
  QueryVectorParams,
10
+ UpdateVectorParams,
7
11
  UpsertVectorParams,
8
12
  } from '@mastra/core/vector';
9
13
  import type { VectorFilter } from '@mastra/core/vector/filter';
@@ -22,11 +26,7 @@ export class UpstashVector extends MastraVector {
22
26
  });
23
27
  }
24
28
 
25
- async upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]> {
26
- const params = this.normalizeArgs<UpsertVectorParams>('upsert', args);
27
-
28
- const { indexName, vectors, metadata, ids } = params;
29
-
29
+ async upsert({ indexName, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]> {
30
30
  const generatedIds = ids || vectors.map(() => crypto.randomUUID());
31
31
 
32
32
  const points = vectors.map((vector, index) => ({
@@ -46,15 +46,17 @@ export class UpstashVector extends MastraVector {
46
46
  return translator.translate(filter);
47
47
  }
48
48
 
49
- async createIndex(..._args: ParamsToArgs<CreateIndexParams>): Promise<void> {
49
+ async createIndex(_params: CreateIndexParams): Promise<void> {
50
50
  console.log('No need to call createIndex for Upstash');
51
51
  }
52
52
 
53
- async query(...args: ParamsToArgs<QueryVectorParams>): Promise<QueryResult[]> {
54
- const params = this.normalizeArgs<QueryVectorParams>('query', args);
55
-
56
- const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;
57
-
53
+ async query({
54
+ indexName,
55
+ queryVector,
56
+ topK = 10,
57
+ filter,
58
+ includeVector = false,
59
+ }: QueryVectorParams): Promise<QueryResult[]> {
58
60
  const ns = this.client.namespace(indexName);
59
61
 
60
62
  const filterString = this.transformFilter(filter);
@@ -80,7 +82,13 @@ export class UpstashVector extends MastraVector {
80
82
  return indexes.filter(Boolean);
81
83
  }
82
84
 
83
- async describeIndex(indexName: string) {
85
+ /**
86
+ * Retrieves statistics about a vector index.
87
+ *
88
+ * @param {string} indexName - The name of the index to describe
89
+ * @returns A promise that resolves to the index statistics including dimension, count and metric
90
+ */
91
+ async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
84
92
  const info = await this.client.info();
85
93
 
86
94
  return {
@@ -90,7 +98,7 @@ export class UpstashVector extends MastraVector {
90
98
  };
91
99
  }
92
100
 
93
- async deleteIndex(indexName: string): Promise<void> {
101
+ async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
94
102
  try {
95
103
  await this.client.deleteNamespace(indexName);
96
104
  } catch (error) {
@@ -98,50 +106,64 @@ export class UpstashVector extends MastraVector {
98
106
  }
99
107
  }
100
108
 
101
- async updateIndexById(
102
- indexName: string,
103
- id: string,
104
- update: {
105
- vector?: number[];
106
- metadata?: Record<string, any>;
107
- },
108
- ): Promise<void> {
109
- if (!update.vector && !update.metadata) {
110
- throw new Error('No update data provided');
111
- }
112
-
113
- // The upstash client throws an exception as: 'This index requires dense vectors' when
114
- // only metadata is present in the update object.
115
- if (!update.vector && update.metadata) {
116
- throw new Error('Both vector and metadata must be provided for an update');
117
- }
118
-
119
- const updatePayload: any = { id: id };
120
- if (update.vector) {
121
- updatePayload.vector = update.vector;
122
- }
123
- if (update.metadata) {
124
- updatePayload.metadata = update.metadata;
109
+ /**
110
+ * Updates a vector by its ID with the provided vector and/or metadata.
111
+ * @param indexName - The name of the index containing the vector.
112
+ * @param id - The ID of the vector to update.
113
+ * @param update - An object containing the vector and/or metadata to update.
114
+ * @param update.vector - An optional array of numbers representing the new vector.
115
+ * @param update.metadata - An optional record containing the new metadata.
116
+ * @returns A promise that resolves when the update is complete.
117
+ * @throws Will throw an error if no updates are provided or if the update operation fails.
118
+ */
119
+ async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
120
+ try {
121
+ if (!update.vector && !update.metadata) {
122
+ throw new Error('No update data provided');
123
+ }
124
+
125
+ // The upstash client throws an exception as: 'This index requires dense vectors' when
126
+ // only metadata is present in the update object.
127
+ if (!update.vector && update.metadata) {
128
+ throw new Error('Both vector and metadata must be provided for an update');
129
+ }
130
+
131
+ const updatePayload: any = { id: id };
132
+ if (update.vector) {
133
+ updatePayload.vector = update.vector;
134
+ }
135
+ if (update.metadata) {
136
+ updatePayload.metadata = update.metadata;
137
+ }
138
+
139
+ const points = {
140
+ id: updatePayload.id,
141
+ vector: updatePayload.vector,
142
+ metadata: updatePayload.metadata,
143
+ };
144
+
145
+ await this.client.upsert(points, {
146
+ namespace: indexName,
147
+ });
148
+ } catch (error: any) {
149
+ throw new Error(`Failed to update vector by id: ${id} for index name: ${indexName}: ${error.message}`);
125
150
  }
126
-
127
- const points = {
128
- id: updatePayload.id,
129
- vector: updatePayload.vector,
130
- metadata: updatePayload.metadata,
131
- };
132
-
133
- await this.client.upsert(points, {
134
- namespace: indexName,
135
- });
136
151
  }
137
152
 
138
- async deleteIndexById(indexName: string, id: string): Promise<void> {
153
+ /**
154
+ * Deletes a vector by its ID.
155
+ * @param indexName - The name of the index containing the vector.
156
+ * @param id - The ID of the vector to delete.
157
+ * @returns A promise that resolves when the deletion is complete.
158
+ * @throws Will throw an error if the deletion operation fails.
159
+ */
160
+ async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
139
161
  try {
140
162
  await this.client.delete(id, {
141
163
  namespace: indexName,
142
164
  });
143
165
  } catch (error) {
144
- console.error('Failed to delete index by ID:', error);
166
+ console.error(`Failed to delete vector by id: ${id} for index name: ${indexName}:`, error);
145
167
  }
146
168
  }
147
169
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Vector store specific prompt that details supported operators and examples.
3
+ * This prompt helps users construct valid filters for Upstash Vector.
4
+ */
5
+ export const UPSTASH_PROMPT = `When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
6
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
7
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
8
+
9
+ Basic Comparison Operators:
10
+ - $eq: Exact match (default when using field: value)
11
+ Example: { "category": "electronics" }
12
+ - $ne: Not equal
13
+ Example: { "category": { "$ne": "electronics" } }
14
+ - $gt: Greater than
15
+ Example: { "price": { "$gt": 100 } }
16
+ - $gte: Greater than or equal
17
+ Example: { "price": { "$gte": 100 } }
18
+ - $lt: Less than
19
+ Example: { "price": { "$lt": 100 } }
20
+ - $lte: Less than or equal
21
+ Example: { "price": { "$lte": 100 } }
22
+
23
+ Array Operators:
24
+ - $in: Match any value in array
25
+ Example: { "category": { "$in": ["electronics", "books"] } }
26
+ - $nin: Does not match any value in array
27
+ Example: { "category": { "$nin": ["electronics", "books"] } }
28
+
29
+ Logical Operators:
30
+ - $and: Logical AND (can be implicit or explicit)
31
+ Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
32
+ Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
33
+ - $or: Logical OR
34
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
35
+
36
+ Element Operators:
37
+ - $exists: Check if field exists
38
+ Example: { "rating": { "$exists": true } }
39
+
40
+ Restrictions:
41
+ - Regex patterns are not supported
42
+ - Only $and and $or logical operators are supported at the top level
43
+ - Empty arrays in $in/$nin will return no results
44
+ - Nested fields are supported using dot notation
45
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
46
+ - At least one key-value pair is required in filter object
47
+ - Empty objects and undefined values are treated as no filter
48
+ - Invalid types in comparison operators will throw errors
49
+ - All non-logical operators must be used within a field condition
50
+ Valid: { "field": { "$gt": 100 } }
51
+ Valid: { "$and": [...] }
52
+ Invalid: { "$gt": 100 }
53
+ - Logical operators must contain field conditions, not direct operators
54
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
55
+ Invalid: { "$and": [{ "$gt": 100 }] }
56
+ - Logical operators ($and, $or):
57
+ - Can only be used at top level or nested within other logical operators
58
+ - Can not be used on a field level, or be nested inside a field
59
+ - Can not be used inside an operator
60
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
61
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
62
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
63
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
64
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
65
+
66
+ Example Complex Query:
67
+ {
68
+ "$and": [
69
+ { "category": { "$in": ["electronics", "computers"] } },
70
+ { "price": { "$gte": 100, "$lte": 1000 } },
71
+ { "rating": { "$exists": true, "$gt": 4 } },
72
+ { "$or": [
73
+ { "stock": { "$gt": 0 } },
74
+ { "preorder": true }
75
+ ]}
76
+ ]
77
+ }`;