@mastra/upstash 0.10.3 → 0.11.0-alpha.1
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/.turbo/turbo-build.log +12 -12
- package/CHANGELOG.md +24 -0
- package/README.md +4 -0
- package/dist/_tsup-dts-rollup.d.cts +44 -9
- package/dist/_tsup-dts-rollup.d.ts +44 -9
- package/dist/index.cjs +1130 -482
- package/dist/index.js +1131 -483
- package/package.json +7 -7
- package/src/storage/index.ts +14 -0
- package/src/storage/upstash.test.ts +25 -1
- package/src/vector/index.ts +54 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/upstash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-alpha.1",
|
|
4
4
|
"description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.8",
|
|
28
|
-
"@types/node": "^20.
|
|
28
|
+
"@types/node": "^20.19.0",
|
|
29
29
|
"dotenv": "^16.5.0",
|
|
30
30
|
"eslint": "^9.28.0",
|
|
31
31
|
"tsup": "^8.5.0",
|
|
32
|
-
"typescript": "^5.8.
|
|
33
|
-
"vitest": "^3.2.
|
|
34
|
-
"@internal/storage-test-utils": "0.0.
|
|
35
|
-
"@internal/lint": "0.0.
|
|
36
|
-
"@mastra/core": "0.10.
|
|
32
|
+
"typescript": "^5.8.3",
|
|
33
|
+
"vitest": "^3.2.3",
|
|
34
|
+
"@internal/storage-test-utils": "0.0.8",
|
|
35
|
+
"@internal/lint": "0.0.12",
|
|
36
|
+
"@mastra/core": "0.10.6-alpha.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
package/src/storage/index.ts
CHANGED
|
@@ -628,10 +628,15 @@ export class UpstashStore extends MastraStorage {
|
|
|
628
628
|
_index: index,
|
|
629
629
|
}));
|
|
630
630
|
|
|
631
|
+
// Get current thread data once (all messages belong to same thread)
|
|
632
|
+
const threadKey = this.getKey(TABLE_THREADS, { id: threadId });
|
|
633
|
+
const existingThread = await this.redis.get<StorageThreadType>(threadKey);
|
|
634
|
+
|
|
631
635
|
const batchSize = 1000;
|
|
632
636
|
for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
|
|
633
637
|
const batch = messagesWithIndex.slice(i, i + batchSize);
|
|
634
638
|
const pipeline = this.redis.pipeline();
|
|
639
|
+
|
|
635
640
|
for (const message of batch) {
|
|
636
641
|
const key = this.getMessageKey(message.threadId!, message.id);
|
|
637
642
|
const createdAtScore = new Date(message.createdAt).getTime();
|
|
@@ -647,6 +652,15 @@ export class UpstashStore extends MastraStorage {
|
|
|
647
652
|
});
|
|
648
653
|
}
|
|
649
654
|
|
|
655
|
+
// Update the thread's updatedAt field (only in the first batch)
|
|
656
|
+
if (i === 0 && existingThread) {
|
|
657
|
+
const updatedThread = {
|
|
658
|
+
...existingThread,
|
|
659
|
+
updatedAt: new Date(),
|
|
660
|
+
};
|
|
661
|
+
pipeline.set(threadKey, this.processRecord(TABLE_THREADS, updatedThread).processedRecord);
|
|
662
|
+
}
|
|
663
|
+
|
|
650
664
|
await pipeline.exec();
|
|
651
665
|
}
|
|
652
666
|
|
|
@@ -187,6 +187,29 @@ describe('UpstashStore', () => {
|
|
|
187
187
|
updated: 'value',
|
|
188
188
|
});
|
|
189
189
|
});
|
|
190
|
+
|
|
191
|
+
it('should update thread updatedAt when a message is saved to it', async () => {
|
|
192
|
+
const thread = createSampleThread();
|
|
193
|
+
await store.saveThread({ thread });
|
|
194
|
+
|
|
195
|
+
// Get the initial thread to capture the original updatedAt
|
|
196
|
+
const initialThread = await store.getThreadById({ threadId: thread.id });
|
|
197
|
+
expect(initialThread).toBeDefined();
|
|
198
|
+
const originalUpdatedAt = initialThread!.updatedAt;
|
|
199
|
+
|
|
200
|
+
// Wait a small amount to ensure different timestamp
|
|
201
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
202
|
+
|
|
203
|
+
// Create and save a message to the thread
|
|
204
|
+
const message = createSampleMessageV2({ threadId: thread.id });
|
|
205
|
+
await store.saveMessages({ messages: [message], format: 'v2' });
|
|
206
|
+
|
|
207
|
+
// Retrieve the thread again and check that updatedAt was updated
|
|
208
|
+
const updatedThread = await store.getThreadById({ threadId: thread.id });
|
|
209
|
+
expect(updatedThread).toBeDefined();
|
|
210
|
+
expect(updatedThread!.updatedAt.getTime()).toBeGreaterThan(originalUpdatedAt.getTime());
|
|
211
|
+
});
|
|
212
|
+
|
|
190
213
|
it('should fetch >100000 threads by resource ID', async () => {
|
|
191
214
|
const resourceId = `resource-${randomUUID()}`;
|
|
192
215
|
const total = 100_000;
|
|
@@ -669,13 +692,14 @@ describe('UpstashStore', () => {
|
|
|
669
692
|
activePaths: [],
|
|
670
693
|
suspendedPaths: {},
|
|
671
694
|
timestamp: Date.now(),
|
|
695
|
+
status: 'success',
|
|
672
696
|
};
|
|
673
697
|
|
|
674
698
|
await store.persistWorkflowSnapshot({
|
|
675
699
|
namespace: testNamespace,
|
|
676
700
|
workflowName: testWorkflow,
|
|
677
701
|
runId: testRunId,
|
|
678
|
-
snapshot: mockSnapshot,
|
|
702
|
+
snapshot: mockSnapshot as WorkflowRunState,
|
|
679
703
|
});
|
|
680
704
|
|
|
681
705
|
const loadedSnapshot = await store.loadWorkflowSnapshot({
|
package/src/vector/index.ts
CHANGED
|
@@ -18,6 +18,12 @@ import { UpstashFilterTranslator } from './filter';
|
|
|
18
18
|
export class UpstashVector extends MastraVector {
|
|
19
19
|
private client: Index;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new UpstashVector instance.
|
|
23
|
+
* @param {object} params - The parameters for the UpstashVector.
|
|
24
|
+
* @param {string} params.url - The URL of the Upstash vector index.
|
|
25
|
+
* @param {string} params.token - The token for the Upstash vector index.
|
|
26
|
+
*/
|
|
21
27
|
constructor({ url, token }: { url: string; token: string }) {
|
|
22
28
|
super();
|
|
23
29
|
this.client = new Index({
|
|
@@ -26,7 +32,12 @@ export class UpstashVector extends MastraVector {
|
|
|
26
32
|
});
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Upserts vectors into the index.
|
|
37
|
+
* @param {UpsertVectorParams} params - The parameters for the upsert operation.
|
|
38
|
+
* @returns {Promise<string[]>} A promise that resolves to the IDs of the upserted vectors.
|
|
39
|
+
*/
|
|
40
|
+
async upsert({ indexName: namespace, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]> {
|
|
30
41
|
const generatedIds = ids || vectors.map(() => crypto.randomUUID());
|
|
31
42
|
|
|
32
43
|
const points = vectors.map((vector, index) => ({
|
|
@@ -36,28 +47,43 @@ export class UpstashVector extends MastraVector {
|
|
|
36
47
|
}));
|
|
37
48
|
|
|
38
49
|
await this.client.upsert(points, {
|
|
39
|
-
namespace
|
|
50
|
+
namespace,
|
|
40
51
|
});
|
|
41
52
|
return generatedIds;
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
57
|
+
* @param {VectorFilter} [filter] - The filter to transform.
|
|
58
|
+
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
59
|
+
*/
|
|
44
60
|
transformFilter(filter?: VectorFilter) {
|
|
45
61
|
const translator = new UpstashFilterTranslator();
|
|
46
62
|
return translator.translate(filter);
|
|
47
63
|
}
|
|
48
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new index. For Upstash, this is a no-op as indexes (known as namespaces in Upstash) are created on-the-fly.
|
|
67
|
+
* @param {CreateIndexParams} _params - The parameters for creating the index (ignored).
|
|
68
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
69
|
+
*/
|
|
49
70
|
async createIndex(_params: CreateIndexParams): Promise<void> {
|
|
50
|
-
|
|
71
|
+
this.logger.debug('No need to call createIndex for Upstash');
|
|
51
72
|
}
|
|
52
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Queries the vector index.
|
|
76
|
+
* @param {QueryVectorParams} params - The parameters for the query operation. indexName is the namespace in Upstash.
|
|
77
|
+
* @returns {Promise<QueryResult[]>} A promise that resolves to the query results.
|
|
78
|
+
*/
|
|
53
79
|
async query({
|
|
54
|
-
indexName,
|
|
80
|
+
indexName: namespace,
|
|
55
81
|
queryVector,
|
|
56
82
|
topK = 10,
|
|
57
83
|
filter,
|
|
58
84
|
includeVector = false,
|
|
59
85
|
}: QueryVectorParams): Promise<QueryResult[]> {
|
|
60
|
-
const ns = this.client.namespace(
|
|
86
|
+
const ns = this.client.namespace(namespace);
|
|
61
87
|
|
|
62
88
|
const filterString = this.transformFilter(filter);
|
|
63
89
|
const results = await ns.query({
|
|
@@ -77,6 +103,10 @@ export class UpstashVector extends MastraVector {
|
|
|
77
103
|
}));
|
|
78
104
|
}
|
|
79
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Lists all namespaces in the Upstash vector index, which correspond to indexes.
|
|
108
|
+
* @returns {Promise<string[]>} A promise that resolves to a list of index names.
|
|
109
|
+
*/
|
|
80
110
|
async listIndexes(): Promise<string[]> {
|
|
81
111
|
const indexes = await this.client.listNamespaces();
|
|
82
112
|
return indexes.filter(Boolean);
|
|
@@ -85,30 +115,35 @@ export class UpstashVector extends MastraVector {
|
|
|
85
115
|
/**
|
|
86
116
|
* Retrieves statistics about a vector index.
|
|
87
117
|
*
|
|
88
|
-
* @param {string} indexName - The name of the
|
|
118
|
+
* @param {string} indexName - The name of the namespace to describe
|
|
89
119
|
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
90
120
|
*/
|
|
91
|
-
async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
|
|
121
|
+
async describeIndex({ indexName: namespace }: DescribeIndexParams): Promise<IndexStats> {
|
|
92
122
|
const info = await this.client.info();
|
|
93
123
|
|
|
94
124
|
return {
|
|
95
125
|
dimension: info.dimension,
|
|
96
|
-
count: info.namespaces?.[
|
|
126
|
+
count: info.namespaces?.[namespace]?.vectorCount || 0,
|
|
97
127
|
metric: info?.similarityFunction?.toLowerCase() as 'cosine' | 'euclidean' | 'dotproduct',
|
|
98
128
|
};
|
|
99
129
|
}
|
|
100
130
|
|
|
101
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Deletes an index (namespace).
|
|
133
|
+
* @param {DeleteIndexParams} params - The parameters for the delete operation.
|
|
134
|
+
* @returns {Promise<void>} A promise that resolves when the deletion is complete.
|
|
135
|
+
*/
|
|
136
|
+
async deleteIndex({ indexName: namespace }: DeleteIndexParams): Promise<void> {
|
|
102
137
|
try {
|
|
103
|
-
await this.client.deleteNamespace(
|
|
138
|
+
await this.client.deleteNamespace(namespace);
|
|
104
139
|
} catch (error) {
|
|
105
|
-
|
|
140
|
+
this.logger.error('Failed to delete namespace:', error);
|
|
106
141
|
}
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
/**
|
|
110
145
|
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
111
|
-
* @param indexName - The name of the
|
|
146
|
+
* @param indexName - The name of the namespace containing the vector.
|
|
112
147
|
* @param id - The ID of the vector to update.
|
|
113
148
|
* @param update - An object containing the vector and/or metadata to update.
|
|
114
149
|
* @param update.vector - An optional array of numbers representing the new vector.
|
|
@@ -116,7 +151,7 @@ export class UpstashVector extends MastraVector {
|
|
|
116
151
|
* @returns A promise that resolves when the update is complete.
|
|
117
152
|
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
118
153
|
*/
|
|
119
|
-
async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
|
|
154
|
+
async updateVector({ indexName: namespace, id, update }: UpdateVectorParams): Promise<void> {
|
|
120
155
|
try {
|
|
121
156
|
if (!update.vector && !update.metadata) {
|
|
122
157
|
throw new Error('No update data provided');
|
|
@@ -143,27 +178,27 @@ export class UpstashVector extends MastraVector {
|
|
|
143
178
|
};
|
|
144
179
|
|
|
145
180
|
await this.client.upsert(points, {
|
|
146
|
-
namespace
|
|
181
|
+
namespace,
|
|
147
182
|
});
|
|
148
183
|
} catch (error: any) {
|
|
149
|
-
throw new Error(`Failed to update vector by id: ${id} for index name: ${
|
|
184
|
+
throw new Error(`Failed to update vector by id: ${id} for index name: ${namespace}: ${error.message}`);
|
|
150
185
|
}
|
|
151
186
|
}
|
|
152
187
|
|
|
153
188
|
/**
|
|
154
189
|
* Deletes a vector by its ID.
|
|
155
|
-
* @param indexName - The name of the
|
|
190
|
+
* @param indexName - The name of the namespace containing the vector.
|
|
156
191
|
* @param id - The ID of the vector to delete.
|
|
157
192
|
* @returns A promise that resolves when the deletion is complete.
|
|
158
193
|
* @throws Will throw an error if the deletion operation fails.
|
|
159
194
|
*/
|
|
160
|
-
async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
|
|
195
|
+
async deleteVector({ indexName: namespace, id }: DeleteVectorParams): Promise<void> {
|
|
161
196
|
try {
|
|
162
197
|
await this.client.delete(id, {
|
|
163
|
-
namespace
|
|
198
|
+
namespace,
|
|
164
199
|
});
|
|
165
200
|
} catch (error) {
|
|
166
|
-
|
|
201
|
+
this.logger.error(`Failed to delete vector by id: ${id} for namespace: ${namespace}:`, error);
|
|
167
202
|
}
|
|
168
203
|
}
|
|
169
204
|
}
|