@mastra/upstash 0.11.1-alpha.1 → 0.11.1-alpha.2
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 +7 -7
- package/CHANGELOG.md +12 -0
- package/dist/_tsup-dts-rollup.d.cts +28 -6
- package/dist/_tsup-dts-rollup.d.ts +28 -6
- package/dist/index.cjs +89 -2
- package/dist/index.js +90 -3
- package/package.json +3 -3
- package/src/storage/index.ts +109 -0
- package/src/storage/upstash.test.ts +75 -0
- package/src/vector/filter.test.ts +7 -6
- package/src/vector/filter.ts +10 -4
- package/src/vector/index.test.ts +2 -2
- package/src/vector/index.ts +7 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/upstash@0.11.1-alpha.
|
|
2
|
+
> @mastra/upstash@0.11.1-alpha.2 build /home/runner/work/mastra/mastra/stores/upstash
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 9310ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 12483ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m58.11 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1435ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m58.93 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1435ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @mastra/upstash
|
|
2
2
|
|
|
3
|
+
## 0.11.1-alpha.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 15e9d26: Added per-resource working memory for LibSQL, Upstash, and PG
|
|
8
|
+
- 0fb9d64: [MASTRA-4018] Update saveMessages in storage adapters to upsert messages
|
|
9
|
+
- Updated dependencies [15e9d26]
|
|
10
|
+
- Updated dependencies [07d6d88]
|
|
11
|
+
- Updated dependencies [5d74aab]
|
|
12
|
+
- Updated dependencies [144eb0b]
|
|
13
|
+
- @mastra/core@0.10.7-alpha.2
|
|
14
|
+
|
|
3
15
|
## 0.11.1-alpha.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -11,6 +11,7 @@ import type { MastraMessageV2 } from '@mastra/core/agent';
|
|
|
11
11
|
import { MastraStorage } from '@mastra/core/storage';
|
|
12
12
|
import { MastraVector } from '@mastra/core/vector';
|
|
13
13
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
14
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
14
15
|
import type { PaginationArgs } from '@mastra/core/storage';
|
|
15
16
|
import type { PaginationInfo } from '@mastra/core/storage';
|
|
16
17
|
import type { QueryResult } from '@mastra/core/vector';
|
|
@@ -18,6 +19,7 @@ import type { QueryVectorParams } from '@mastra/core/vector';
|
|
|
18
19
|
import type { StorageColumn } from '@mastra/core/storage';
|
|
19
20
|
import type { StorageGetMessagesArg } from '@mastra/core/storage';
|
|
20
21
|
import type { StorageGetTracesArg } from '@mastra/core/storage';
|
|
22
|
+
import type { StorageResourceType } from '@mastra/core/storage';
|
|
21
23
|
import type { StorageThreadType } from '@mastra/core/memory';
|
|
22
24
|
import type { TABLE_NAMES } from '@mastra/core/storage';
|
|
23
25
|
import type { UpdateVectorParams } from '@mastra/core/vector';
|
|
@@ -42,9 +44,9 @@ declare interface UpstashConfig {
|
|
|
42
44
|
export { UpstashConfig }
|
|
43
45
|
export { UpstashConfig as UpstashConfig_alias_1 }
|
|
44
46
|
|
|
45
|
-
export declare class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
47
|
+
export declare class UpstashFilterTranslator extends BaseFilterTranslator<UpstashVectorFilter, string | undefined> {
|
|
46
48
|
protected getSupportedOperators(): OperatorSupport;
|
|
47
|
-
translate(filter?:
|
|
49
|
+
translate(filter?: UpstashVectorFilter): string | undefined;
|
|
48
50
|
private translateNode;
|
|
49
51
|
private readonly COMPARISON_OPS;
|
|
50
52
|
private translateOperator;
|
|
@@ -56,11 +58,18 @@ export declare class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
|
56
58
|
private joinConditions;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
declare type UpstashOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch'> & {
|
|
62
|
+
$contains: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
declare type UpstashQueryVectorParams = QueryVectorParams<UpstashVectorFilter>;
|
|
66
|
+
|
|
59
67
|
declare class UpstashStore extends MastraStorage {
|
|
60
68
|
private redis;
|
|
61
69
|
constructor(config: UpstashConfig);
|
|
62
70
|
get supports(): {
|
|
63
71
|
selectByIncludeResourceScope: boolean;
|
|
72
|
+
resourceWorkingMemory: boolean;
|
|
64
73
|
};
|
|
65
74
|
private transformEvalRecord;
|
|
66
75
|
private parseJSON;
|
|
@@ -221,11 +230,22 @@ declare class UpstashStore extends MastraStorage {
|
|
|
221
230
|
};
|
|
222
231
|
}[];
|
|
223
232
|
}): Promise<MastraMessageV2[]>;
|
|
233
|
+
getResourceById({ resourceId }: {
|
|
234
|
+
resourceId: string;
|
|
235
|
+
}): Promise<StorageResourceType | null>;
|
|
236
|
+
saveResource({ resource }: {
|
|
237
|
+
resource: StorageResourceType;
|
|
238
|
+
}): Promise<StorageResourceType>;
|
|
239
|
+
updateResource({ resourceId, workingMemory, metadata, }: {
|
|
240
|
+
resourceId: string;
|
|
241
|
+
workingMemory?: string;
|
|
242
|
+
metadata?: Record<string, unknown>;
|
|
243
|
+
}): Promise<StorageResourceType>;
|
|
224
244
|
}
|
|
225
245
|
export { UpstashStore }
|
|
226
246
|
export { UpstashStore as UpstashStore_alias_1 }
|
|
227
247
|
|
|
228
|
-
declare class UpstashVector extends MastraVector {
|
|
248
|
+
declare class UpstashVector extends MastraVector<UpstashVectorFilter> {
|
|
229
249
|
private client;
|
|
230
250
|
/**
|
|
231
251
|
* Creates a new UpstashVector instance.
|
|
@@ -245,10 +265,10 @@ declare class UpstashVector extends MastraVector {
|
|
|
245
265
|
upsert({ indexName: namespace, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]>;
|
|
246
266
|
/**
|
|
247
267
|
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
248
|
-
* @param {
|
|
268
|
+
* @param {UpstashVectorFilter} [filter] - The filter to transform.
|
|
249
269
|
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
250
270
|
*/
|
|
251
|
-
transformFilter(filter?:
|
|
271
|
+
transformFilter(filter?: UpstashVectorFilter): string | undefined;
|
|
252
272
|
/**
|
|
253
273
|
* Creates a new index. For Upstash, this is a no-op as indexes (known as namespaces in Upstash) are created on-the-fly.
|
|
254
274
|
* @param {CreateIndexParams} _params - The parameters for creating the index (ignored).
|
|
@@ -260,7 +280,7 @@ declare class UpstashVector extends MastraVector {
|
|
|
260
280
|
* @param {QueryVectorParams} params - The parameters for the query operation. indexName is the namespace in Upstash.
|
|
261
281
|
* @returns {Promise<QueryResult[]>} A promise that resolves to the query results.
|
|
262
282
|
*/
|
|
263
|
-
query({ indexName: namespace, queryVector, topK, filter, includeVector, }:
|
|
283
|
+
query({ indexName: namespace, queryVector, topK, filter, includeVector, }: UpstashQueryVectorParams): Promise<QueryResult[]>;
|
|
264
284
|
/**
|
|
265
285
|
* Lists all namespaces in the Upstash vector index, which correspond to indexes.
|
|
266
286
|
* @returns {Promise<string[]>} A promise that resolves to a list of index names.
|
|
@@ -302,4 +322,6 @@ declare class UpstashVector extends MastraVector {
|
|
|
302
322
|
export { UpstashVector }
|
|
303
323
|
export { UpstashVector as UpstashVector_alias_1 }
|
|
304
324
|
|
|
325
|
+
export declare type UpstashVectorFilter = VectorFilter<keyof UpstashOperatorValueMap, UpstashOperatorValueMap>;
|
|
326
|
+
|
|
305
327
|
export { }
|
|
@@ -11,6 +11,7 @@ import type { MastraMessageV2 } from '@mastra/core/agent';
|
|
|
11
11
|
import { MastraStorage } from '@mastra/core/storage';
|
|
12
12
|
import { MastraVector } from '@mastra/core/vector';
|
|
13
13
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
14
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
14
15
|
import type { PaginationArgs } from '@mastra/core/storage';
|
|
15
16
|
import type { PaginationInfo } from '@mastra/core/storage';
|
|
16
17
|
import type { QueryResult } from '@mastra/core/vector';
|
|
@@ -18,6 +19,7 @@ import type { QueryVectorParams } from '@mastra/core/vector';
|
|
|
18
19
|
import type { StorageColumn } from '@mastra/core/storage';
|
|
19
20
|
import type { StorageGetMessagesArg } from '@mastra/core/storage';
|
|
20
21
|
import type { StorageGetTracesArg } from '@mastra/core/storage';
|
|
22
|
+
import type { StorageResourceType } from '@mastra/core/storage';
|
|
21
23
|
import type { StorageThreadType } from '@mastra/core/memory';
|
|
22
24
|
import type { TABLE_NAMES } from '@mastra/core/storage';
|
|
23
25
|
import type { UpdateVectorParams } from '@mastra/core/vector';
|
|
@@ -42,9 +44,9 @@ declare interface UpstashConfig {
|
|
|
42
44
|
export { UpstashConfig }
|
|
43
45
|
export { UpstashConfig as UpstashConfig_alias_1 }
|
|
44
46
|
|
|
45
|
-
export declare class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
47
|
+
export declare class UpstashFilterTranslator extends BaseFilterTranslator<UpstashVectorFilter, string | undefined> {
|
|
46
48
|
protected getSupportedOperators(): OperatorSupport;
|
|
47
|
-
translate(filter?:
|
|
49
|
+
translate(filter?: UpstashVectorFilter): string | undefined;
|
|
48
50
|
private translateNode;
|
|
49
51
|
private readonly COMPARISON_OPS;
|
|
50
52
|
private translateOperator;
|
|
@@ -56,11 +58,18 @@ export declare class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
|
56
58
|
private joinConditions;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
declare type UpstashOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch'> & {
|
|
62
|
+
$contains: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
declare type UpstashQueryVectorParams = QueryVectorParams<UpstashVectorFilter>;
|
|
66
|
+
|
|
59
67
|
declare class UpstashStore extends MastraStorage {
|
|
60
68
|
private redis;
|
|
61
69
|
constructor(config: UpstashConfig);
|
|
62
70
|
get supports(): {
|
|
63
71
|
selectByIncludeResourceScope: boolean;
|
|
72
|
+
resourceWorkingMemory: boolean;
|
|
64
73
|
};
|
|
65
74
|
private transformEvalRecord;
|
|
66
75
|
private parseJSON;
|
|
@@ -221,11 +230,22 @@ declare class UpstashStore extends MastraStorage {
|
|
|
221
230
|
};
|
|
222
231
|
}[];
|
|
223
232
|
}): Promise<MastraMessageV2[]>;
|
|
233
|
+
getResourceById({ resourceId }: {
|
|
234
|
+
resourceId: string;
|
|
235
|
+
}): Promise<StorageResourceType | null>;
|
|
236
|
+
saveResource({ resource }: {
|
|
237
|
+
resource: StorageResourceType;
|
|
238
|
+
}): Promise<StorageResourceType>;
|
|
239
|
+
updateResource({ resourceId, workingMemory, metadata, }: {
|
|
240
|
+
resourceId: string;
|
|
241
|
+
workingMemory?: string;
|
|
242
|
+
metadata?: Record<string, unknown>;
|
|
243
|
+
}): Promise<StorageResourceType>;
|
|
224
244
|
}
|
|
225
245
|
export { UpstashStore }
|
|
226
246
|
export { UpstashStore as UpstashStore_alias_1 }
|
|
227
247
|
|
|
228
|
-
declare class UpstashVector extends MastraVector {
|
|
248
|
+
declare class UpstashVector extends MastraVector<UpstashVectorFilter> {
|
|
229
249
|
private client;
|
|
230
250
|
/**
|
|
231
251
|
* Creates a new UpstashVector instance.
|
|
@@ -245,10 +265,10 @@ declare class UpstashVector extends MastraVector {
|
|
|
245
265
|
upsert({ indexName: namespace, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]>;
|
|
246
266
|
/**
|
|
247
267
|
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
248
|
-
* @param {
|
|
268
|
+
* @param {UpstashVectorFilter} [filter] - The filter to transform.
|
|
249
269
|
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
250
270
|
*/
|
|
251
|
-
transformFilter(filter?:
|
|
271
|
+
transformFilter(filter?: UpstashVectorFilter): string | undefined;
|
|
252
272
|
/**
|
|
253
273
|
* Creates a new index. For Upstash, this is a no-op as indexes (known as namespaces in Upstash) are created on-the-fly.
|
|
254
274
|
* @param {CreateIndexParams} _params - The parameters for creating the index (ignored).
|
|
@@ -260,7 +280,7 @@ declare class UpstashVector extends MastraVector {
|
|
|
260
280
|
* @param {QueryVectorParams} params - The parameters for the query operation. indexName is the namespace in Upstash.
|
|
261
281
|
* @returns {Promise<QueryResult[]>} A promise that resolves to the query results.
|
|
262
282
|
*/
|
|
263
|
-
query({ indexName: namespace, queryVector, topK, filter, includeVector, }:
|
|
283
|
+
query({ indexName: namespace, queryVector, topK, filter, includeVector, }: UpstashQueryVectorParams): Promise<QueryResult[]>;
|
|
264
284
|
/**
|
|
265
285
|
* Lists all namespaces in the Upstash vector index, which correspond to indexes.
|
|
266
286
|
* @returns {Promise<string[]>} A promise that resolves to a list of index names.
|
|
@@ -302,4 +322,6 @@ declare class UpstashVector extends MastraVector {
|
|
|
302
322
|
export { UpstashVector }
|
|
303
323
|
export { UpstashVector as UpstashVector_alias_1 }
|
|
304
324
|
|
|
325
|
+
export declare type UpstashVectorFilter = VectorFilter<keyof UpstashOperatorValueMap, UpstashOperatorValueMap>;
|
|
326
|
+
|
|
305
327
|
export { }
|
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,8 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
20
20
|
}
|
|
21
21
|
get supports() {
|
|
22
22
|
return {
|
|
23
|
-
selectByIncludeResourceScope: true
|
|
23
|
+
selectByIncludeResourceScope: true,
|
|
24
|
+
resourceWorkingMemory: true
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
transformEvalRecord(record) {
|
|
@@ -698,6 +699,23 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
698
699
|
const key = this.getMessageKey(message.threadId, message.id);
|
|
699
700
|
const createdAtScore = new Date(message.createdAt).getTime();
|
|
700
701
|
const score = message._index !== void 0 ? message._index : createdAtScore;
|
|
702
|
+
const existingKeyPattern = this.getMessageKey("*", message.id);
|
|
703
|
+
const keys = await this.scanKeys(existingKeyPattern);
|
|
704
|
+
if (keys.length > 0) {
|
|
705
|
+
const pipeline2 = this.redis.pipeline();
|
|
706
|
+
keys.forEach((key2) => pipeline2.get(key2));
|
|
707
|
+
const results = await pipeline2.exec();
|
|
708
|
+
const existingMessages = results.filter(
|
|
709
|
+
(msg) => msg !== null
|
|
710
|
+
);
|
|
711
|
+
for (const existingMessage of existingMessages) {
|
|
712
|
+
const existingMessageKey = this.getMessageKey(existingMessage.threadId, existingMessage.id);
|
|
713
|
+
if (existingMessage && existingMessage.threadId !== message.threadId) {
|
|
714
|
+
pipeline.del(existingMessageKey);
|
|
715
|
+
pipeline.zrem(this.getThreadMessagesKey(existingMessage.threadId), existingMessage.id);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
701
719
|
pipeline.set(key, message);
|
|
702
720
|
pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
|
|
703
721
|
score,
|
|
@@ -1173,6 +1191,75 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
1173
1191
|
this.logger.error("updateMessages is not yet implemented in UpstashStore");
|
|
1174
1192
|
throw new Error("Method not implemented");
|
|
1175
1193
|
}
|
|
1194
|
+
async getResourceById({ resourceId }) {
|
|
1195
|
+
try {
|
|
1196
|
+
const key = `${storage.TABLE_RESOURCES}:${resourceId}`;
|
|
1197
|
+
const data = await this.redis.get(key);
|
|
1198
|
+
if (!data) {
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
...data,
|
|
1203
|
+
createdAt: new Date(data.createdAt),
|
|
1204
|
+
updatedAt: new Date(data.updatedAt),
|
|
1205
|
+
// Ensure workingMemory is always returned as a string, regardless of automatic parsing
|
|
1206
|
+
workingMemory: typeof data.workingMemory === "object" ? JSON.stringify(data.workingMemory) : data.workingMemory,
|
|
1207
|
+
metadata: typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata
|
|
1208
|
+
};
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
this.logger.error("Error getting resource by ID:", error);
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
async saveResource({ resource }) {
|
|
1215
|
+
try {
|
|
1216
|
+
const key = `${storage.TABLE_RESOURCES}:${resource.id}`;
|
|
1217
|
+
const serializedResource = {
|
|
1218
|
+
...resource,
|
|
1219
|
+
metadata: JSON.stringify(resource.metadata),
|
|
1220
|
+
createdAt: resource.createdAt.toISOString(),
|
|
1221
|
+
updatedAt: resource.updatedAt.toISOString()
|
|
1222
|
+
};
|
|
1223
|
+
await this.redis.set(key, serializedResource);
|
|
1224
|
+
return resource;
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
this.logger.error("Error saving resource:", error);
|
|
1227
|
+
throw error;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
async updateResource({
|
|
1231
|
+
resourceId,
|
|
1232
|
+
workingMemory,
|
|
1233
|
+
metadata
|
|
1234
|
+
}) {
|
|
1235
|
+
try {
|
|
1236
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
1237
|
+
if (!existingResource) {
|
|
1238
|
+
const newResource = {
|
|
1239
|
+
id: resourceId,
|
|
1240
|
+
workingMemory,
|
|
1241
|
+
metadata: metadata || {},
|
|
1242
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1243
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1244
|
+
};
|
|
1245
|
+
return this.saveResource({ resource: newResource });
|
|
1246
|
+
}
|
|
1247
|
+
const updatedResource = {
|
|
1248
|
+
...existingResource,
|
|
1249
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
1250
|
+
metadata: {
|
|
1251
|
+
...existingResource.metadata,
|
|
1252
|
+
...metadata
|
|
1253
|
+
},
|
|
1254
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1255
|
+
};
|
|
1256
|
+
await this.saveResource({ resource: updatedResource });
|
|
1257
|
+
return updatedResource;
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
this.logger.error("Error updating resource:", error);
|
|
1260
|
+
throw error;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1176
1263
|
};
|
|
1177
1264
|
var UpstashFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
1178
1265
|
getSupportedOperators() {
|
|
@@ -1413,7 +1500,7 @@ var UpstashVector = class extends vector.MastraVector {
|
|
|
1413
1500
|
}
|
|
1414
1501
|
/**
|
|
1415
1502
|
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
1416
|
-
* @param {
|
|
1503
|
+
* @param {UpstashVectorFilter} [filter] - The filter to transform.
|
|
1417
1504
|
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
1418
1505
|
*/
|
|
1419
1506
|
transformFilter(filter) {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MessageList } from '@mastra/core/agent';
|
|
2
2
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
3
|
-
import { MastraStorage, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_TRACES, TABLE_THREADS } from '@mastra/core/storage';
|
|
3
|
+
import { MastraStorage, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_TRACES, TABLE_THREADS, TABLE_RESOURCES } from '@mastra/core/storage';
|
|
4
4
|
import { Redis } from '@upstash/redis';
|
|
5
5
|
import { MastraVector } from '@mastra/core/vector';
|
|
6
6
|
import { Index } from '@upstash/vector';
|
|
@@ -18,7 +18,8 @@ var UpstashStore = class extends MastraStorage {
|
|
|
18
18
|
}
|
|
19
19
|
get supports() {
|
|
20
20
|
return {
|
|
21
|
-
selectByIncludeResourceScope: true
|
|
21
|
+
selectByIncludeResourceScope: true,
|
|
22
|
+
resourceWorkingMemory: true
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
transformEvalRecord(record) {
|
|
@@ -696,6 +697,23 @@ var UpstashStore = class extends MastraStorage {
|
|
|
696
697
|
const key = this.getMessageKey(message.threadId, message.id);
|
|
697
698
|
const createdAtScore = new Date(message.createdAt).getTime();
|
|
698
699
|
const score = message._index !== void 0 ? message._index : createdAtScore;
|
|
700
|
+
const existingKeyPattern = this.getMessageKey("*", message.id);
|
|
701
|
+
const keys = await this.scanKeys(existingKeyPattern);
|
|
702
|
+
if (keys.length > 0) {
|
|
703
|
+
const pipeline2 = this.redis.pipeline();
|
|
704
|
+
keys.forEach((key2) => pipeline2.get(key2));
|
|
705
|
+
const results = await pipeline2.exec();
|
|
706
|
+
const existingMessages = results.filter(
|
|
707
|
+
(msg) => msg !== null
|
|
708
|
+
);
|
|
709
|
+
for (const existingMessage of existingMessages) {
|
|
710
|
+
const existingMessageKey = this.getMessageKey(existingMessage.threadId, existingMessage.id);
|
|
711
|
+
if (existingMessage && existingMessage.threadId !== message.threadId) {
|
|
712
|
+
pipeline.del(existingMessageKey);
|
|
713
|
+
pipeline.zrem(this.getThreadMessagesKey(existingMessage.threadId), existingMessage.id);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
699
717
|
pipeline.set(key, message);
|
|
700
718
|
pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
|
|
701
719
|
score,
|
|
@@ -1171,6 +1189,75 @@ var UpstashStore = class extends MastraStorage {
|
|
|
1171
1189
|
this.logger.error("updateMessages is not yet implemented in UpstashStore");
|
|
1172
1190
|
throw new Error("Method not implemented");
|
|
1173
1191
|
}
|
|
1192
|
+
async getResourceById({ resourceId }) {
|
|
1193
|
+
try {
|
|
1194
|
+
const key = `${TABLE_RESOURCES}:${resourceId}`;
|
|
1195
|
+
const data = await this.redis.get(key);
|
|
1196
|
+
if (!data) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
...data,
|
|
1201
|
+
createdAt: new Date(data.createdAt),
|
|
1202
|
+
updatedAt: new Date(data.updatedAt),
|
|
1203
|
+
// Ensure workingMemory is always returned as a string, regardless of automatic parsing
|
|
1204
|
+
workingMemory: typeof data.workingMemory === "object" ? JSON.stringify(data.workingMemory) : data.workingMemory,
|
|
1205
|
+
metadata: typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata
|
|
1206
|
+
};
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
this.logger.error("Error getting resource by ID:", error);
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
async saveResource({ resource }) {
|
|
1213
|
+
try {
|
|
1214
|
+
const key = `${TABLE_RESOURCES}:${resource.id}`;
|
|
1215
|
+
const serializedResource = {
|
|
1216
|
+
...resource,
|
|
1217
|
+
metadata: JSON.stringify(resource.metadata),
|
|
1218
|
+
createdAt: resource.createdAt.toISOString(),
|
|
1219
|
+
updatedAt: resource.updatedAt.toISOString()
|
|
1220
|
+
};
|
|
1221
|
+
await this.redis.set(key, serializedResource);
|
|
1222
|
+
return resource;
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
this.logger.error("Error saving resource:", error);
|
|
1225
|
+
throw error;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async updateResource({
|
|
1229
|
+
resourceId,
|
|
1230
|
+
workingMemory,
|
|
1231
|
+
metadata
|
|
1232
|
+
}) {
|
|
1233
|
+
try {
|
|
1234
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
1235
|
+
if (!existingResource) {
|
|
1236
|
+
const newResource = {
|
|
1237
|
+
id: resourceId,
|
|
1238
|
+
workingMemory,
|
|
1239
|
+
metadata: metadata || {},
|
|
1240
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1241
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1242
|
+
};
|
|
1243
|
+
return this.saveResource({ resource: newResource });
|
|
1244
|
+
}
|
|
1245
|
+
const updatedResource = {
|
|
1246
|
+
...existingResource,
|
|
1247
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
1248
|
+
metadata: {
|
|
1249
|
+
...existingResource.metadata,
|
|
1250
|
+
...metadata
|
|
1251
|
+
},
|
|
1252
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1253
|
+
};
|
|
1254
|
+
await this.saveResource({ resource: updatedResource });
|
|
1255
|
+
return updatedResource;
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
this.logger.error("Error updating resource:", error);
|
|
1258
|
+
throw error;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1174
1261
|
};
|
|
1175
1262
|
var UpstashFilterTranslator = class extends BaseFilterTranslator {
|
|
1176
1263
|
getSupportedOperators() {
|
|
@@ -1411,7 +1498,7 @@ var UpstashVector = class extends MastraVector {
|
|
|
1411
1498
|
}
|
|
1412
1499
|
/**
|
|
1413
1500
|
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
1414
|
-
* @param {
|
|
1501
|
+
* @param {UpstashVectorFilter} [filter] - The filter to transform.
|
|
1415
1502
|
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
1416
1503
|
*/
|
|
1417
1504
|
transformFilter(filter) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/upstash",
|
|
3
|
-
"version": "0.11.1-alpha.
|
|
3
|
+
"version": "0.11.1-alpha.2",
|
|
4
4
|
"description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.8",
|
|
28
28
|
"@types/node": "^20.19.0",
|
|
29
29
|
"dotenv": "^16.5.0",
|
|
30
|
-
"eslint": "^9.
|
|
30
|
+
"eslint": "^9.29.0",
|
|
31
31
|
"tsup": "^8.5.0",
|
|
32
32
|
"typescript": "^5.8.3",
|
|
33
33
|
"vitest": "^3.2.3",
|
|
34
34
|
"@internal/lint": "0.0.13",
|
|
35
35
|
"@internal/storage-test-utils": "0.0.9",
|
|
36
|
-
"@mastra/core": "0.10.7-alpha.
|
|
36
|
+
"@mastra/core": "0.10.7-alpha.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
package/src/storage/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
MastraStorage,
|
|
8
8
|
TABLE_MESSAGES,
|
|
9
9
|
TABLE_THREADS,
|
|
10
|
+
TABLE_RESOURCES,
|
|
10
11
|
TABLE_WORKFLOW_SNAPSHOT,
|
|
11
12
|
TABLE_EVALS,
|
|
12
13
|
TABLE_TRACES,
|
|
@@ -15,6 +16,7 @@ import type {
|
|
|
15
16
|
TABLE_NAMES,
|
|
16
17
|
StorageColumn,
|
|
17
18
|
StorageGetMessagesArg,
|
|
19
|
+
StorageResourceType,
|
|
18
20
|
EvalRow,
|
|
19
21
|
WorkflowRuns,
|
|
20
22
|
WorkflowRun,
|
|
@@ -43,9 +45,11 @@ export class UpstashStore extends MastraStorage {
|
|
|
43
45
|
|
|
44
46
|
public get supports(): {
|
|
45
47
|
selectByIncludeResourceScope: boolean;
|
|
48
|
+
resourceWorkingMemory: boolean;
|
|
46
49
|
} {
|
|
47
50
|
return {
|
|
48
51
|
selectByIncludeResourceScope: true,
|
|
52
|
+
resourceWorkingMemory: true,
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -853,6 +857,27 @@ export class UpstashStore extends MastraStorage {
|
|
|
853
857
|
const createdAtScore = new Date(message.createdAt).getTime();
|
|
854
858
|
const score = message._index !== undefined ? message._index : createdAtScore;
|
|
855
859
|
|
|
860
|
+
// Check if this message id exists in another thread
|
|
861
|
+
const existingKeyPattern = this.getMessageKey('*', message.id);
|
|
862
|
+
const keys = await this.scanKeys(existingKeyPattern);
|
|
863
|
+
|
|
864
|
+
if (keys.length > 0) {
|
|
865
|
+
const pipeline2 = this.redis.pipeline();
|
|
866
|
+
keys.forEach(key => pipeline2.get(key));
|
|
867
|
+
const results = await pipeline2.exec();
|
|
868
|
+
const existingMessages = results.filter(
|
|
869
|
+
(msg): msg is MastraMessageV2 | MastraMessageV1 => msg !== null,
|
|
870
|
+
) as (MastraMessageV2 | MastraMessageV1)[];
|
|
871
|
+
for (const existingMessage of existingMessages) {
|
|
872
|
+
const existingMessageKey = this.getMessageKey(existingMessage.threadId!, existingMessage.id);
|
|
873
|
+
if (existingMessage && existingMessage.threadId !== message.threadId) {
|
|
874
|
+
pipeline.del(existingMessageKey);
|
|
875
|
+
// Remove from old thread's sorted set
|
|
876
|
+
pipeline.zrem(this.getThreadMessagesKey(existingMessage.threadId!), existingMessage.id);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
856
881
|
// Store the message data
|
|
857
882
|
pipeline.set(key, message);
|
|
858
883
|
|
|
@@ -1508,4 +1533,88 @@ export class UpstashStore extends MastraStorage {
|
|
|
1508
1533
|
this.logger.error('updateMessages is not yet implemented in UpstashStore');
|
|
1509
1534
|
throw new Error('Method not implemented');
|
|
1510
1535
|
}
|
|
1536
|
+
|
|
1537
|
+
async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
|
|
1538
|
+
try {
|
|
1539
|
+
const key = `${TABLE_RESOURCES}:${resourceId}`;
|
|
1540
|
+
const data = await this.redis.get<StorageResourceType>(key);
|
|
1541
|
+
|
|
1542
|
+
if (!data) {
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
return {
|
|
1547
|
+
...data,
|
|
1548
|
+
createdAt: new Date(data.createdAt),
|
|
1549
|
+
updatedAt: new Date(data.updatedAt),
|
|
1550
|
+
// Ensure workingMemory is always returned as a string, regardless of automatic parsing
|
|
1551
|
+
workingMemory: typeof data.workingMemory === 'object' ? JSON.stringify(data.workingMemory) : data.workingMemory,
|
|
1552
|
+
metadata: typeof data.metadata === 'string' ? JSON.parse(data.metadata) : data.metadata,
|
|
1553
|
+
};
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
this.logger.error('Error getting resource by ID:', error);
|
|
1556
|
+
throw error;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
|
|
1561
|
+
try {
|
|
1562
|
+
const key = `${TABLE_RESOURCES}:${resource.id}`;
|
|
1563
|
+
const serializedResource = {
|
|
1564
|
+
...resource,
|
|
1565
|
+
metadata: JSON.stringify(resource.metadata),
|
|
1566
|
+
createdAt: resource.createdAt.toISOString(),
|
|
1567
|
+
updatedAt: resource.updatedAt.toISOString(),
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
await this.redis.set(key, serializedResource);
|
|
1571
|
+
|
|
1572
|
+
return resource;
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
this.logger.error('Error saving resource:', error);
|
|
1575
|
+
throw error;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
async updateResource({
|
|
1580
|
+
resourceId,
|
|
1581
|
+
workingMemory,
|
|
1582
|
+
metadata,
|
|
1583
|
+
}: {
|
|
1584
|
+
resourceId: string;
|
|
1585
|
+
workingMemory?: string;
|
|
1586
|
+
metadata?: Record<string, unknown>;
|
|
1587
|
+
}): Promise<StorageResourceType> {
|
|
1588
|
+
try {
|
|
1589
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
1590
|
+
|
|
1591
|
+
if (!existingResource) {
|
|
1592
|
+
// Create new resource if it doesn't exist
|
|
1593
|
+
const newResource: StorageResourceType = {
|
|
1594
|
+
id: resourceId,
|
|
1595
|
+
workingMemory,
|
|
1596
|
+
metadata: metadata || {},
|
|
1597
|
+
createdAt: new Date(),
|
|
1598
|
+
updatedAt: new Date(),
|
|
1599
|
+
};
|
|
1600
|
+
return this.saveResource({ resource: newResource });
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const updatedResource = {
|
|
1604
|
+
...existingResource,
|
|
1605
|
+
workingMemory: workingMemory !== undefined ? workingMemory : existingResource.workingMemory,
|
|
1606
|
+
metadata: {
|
|
1607
|
+
...existingResource.metadata,
|
|
1608
|
+
...metadata,
|
|
1609
|
+
},
|
|
1610
|
+
updatedAt: new Date(),
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
await this.saveResource({ resource: updatedResource });
|
|
1614
|
+
return updatedResource;
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
this.logger.error('Error updating resource:', error);
|
|
1617
|
+
throw error;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1511
1620
|
}
|
|
@@ -498,6 +498,81 @@ describe('UpstashStore', () => {
|
|
|
498
498
|
expect(retrievedMessages[0].content).toEqual(messages[0].content);
|
|
499
499
|
});
|
|
500
500
|
|
|
501
|
+
it('should upsert messages: duplicate id+threadId results in update, not duplicate row', async () => {
|
|
502
|
+
const thread = await createSampleThread();
|
|
503
|
+
await store.saveThread({ thread });
|
|
504
|
+
const baseMessage = createSampleMessageV2({
|
|
505
|
+
threadId: thread.id,
|
|
506
|
+
createdAt: new Date(),
|
|
507
|
+
content: { content: 'Original' },
|
|
508
|
+
resourceId: thread.resourceId,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Insert the message for the first time
|
|
512
|
+
await store.saveMessages({ messages: [baseMessage], format: 'v2' });
|
|
513
|
+
|
|
514
|
+
// Insert again with the same id and threadId but different content
|
|
515
|
+
const updatedMessage = {
|
|
516
|
+
...createSampleMessageV2({
|
|
517
|
+
threadId: thread.id,
|
|
518
|
+
createdAt: new Date(),
|
|
519
|
+
content: { content: 'Updated' },
|
|
520
|
+
resourceId: thread.resourceId,
|
|
521
|
+
}),
|
|
522
|
+
id: baseMessage.id,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
await store.saveMessages({ messages: [updatedMessage], format: 'v2' });
|
|
526
|
+
|
|
527
|
+
// Retrieve messages for the thread
|
|
528
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v2' });
|
|
529
|
+
|
|
530
|
+
// Only one message should exist for that id+threadId
|
|
531
|
+
expect(retrievedMessages.filter(m => m.id === baseMessage.id)).toHaveLength(1);
|
|
532
|
+
|
|
533
|
+
// The content should be the updated one
|
|
534
|
+
expect(retrievedMessages.find(m => m.id === baseMessage.id)?.content.content).toBe('Updated');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should upsert messages: duplicate id and different threadid', async () => {
|
|
538
|
+
const thread1 = await createSampleThread();
|
|
539
|
+
const thread2 = await createSampleThread();
|
|
540
|
+
await store.saveThread({ thread: thread1 });
|
|
541
|
+
await store.saveThread({ thread: thread2 });
|
|
542
|
+
|
|
543
|
+
const message = createSampleMessageV2({
|
|
544
|
+
threadId: thread1.id,
|
|
545
|
+
createdAt: new Date(),
|
|
546
|
+
content: { content: 'Thread1 Content' },
|
|
547
|
+
resourceId: thread1.resourceId,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Insert message into thread1
|
|
551
|
+
await store.saveMessages({ messages: [message], format: 'v2' });
|
|
552
|
+
|
|
553
|
+
// Attempt to insert a message with the same id but different threadId
|
|
554
|
+
const conflictingMessage = {
|
|
555
|
+
...createSampleMessageV2({
|
|
556
|
+
threadId: thread2.id, // different thread
|
|
557
|
+
content: { content: 'Thread2 Content' },
|
|
558
|
+
resourceId: thread2.resourceId,
|
|
559
|
+
}),
|
|
560
|
+
id: message.id,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// Save should move the message to the new thread
|
|
564
|
+
await store.saveMessages({ messages: [conflictingMessage], format: 'v2' });
|
|
565
|
+
|
|
566
|
+
// Retrieve messages for both threads
|
|
567
|
+
const thread1Messages = await store.getMessages({ threadId: thread1.id, format: 'v2' });
|
|
568
|
+
const thread2Messages = await store.getMessages({ threadId: thread2.id, format: 'v2' });
|
|
569
|
+
|
|
570
|
+
// Thread 1 should NOT have the message with that id
|
|
571
|
+
expect(thread1Messages.find(m => m.id === message.id)).toBeUndefined();
|
|
572
|
+
|
|
573
|
+
// Thread 2 should have the message with that id
|
|
574
|
+
expect(thread2Messages.find(m => m.id === message.id)?.content.content).toBe('Thread2 Content');
|
|
575
|
+
});
|
|
501
576
|
describe('getMessagesPaginated', () => {
|
|
502
577
|
it('should return paginated messages with total count', async () => {
|
|
503
578
|
const thread = createSampleThread();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import type { UpstashVectorFilter } from './filter';
|
|
3
4
|
import { UpstashFilterTranslator } from './filter';
|
|
4
5
|
|
|
5
6
|
describe('UpstashFilterTranslator', () => {
|
|
@@ -22,7 +23,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
22
23
|
|
|
23
24
|
it('translates nested paths', () => {
|
|
24
25
|
expect(translator.translate({ 'geography.continent': 'Asia' })).toBe("geography.continent = 'Asia'");
|
|
25
|
-
expect(translator.translate({ geography: { continent: 'Asia' } })).toBe("geography.continent = 'Asia'");
|
|
26
|
+
expect(translator.translate({ geography: { continent: 'Asia' } } as any)).toBe("geography.continent = 'Asia'");
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
it('translates comparison operators', () => {
|
|
@@ -65,7 +66,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
it('translates complex nested conditions', () => {
|
|
68
|
-
const filter = {
|
|
69
|
+
const filter: UpstashVectorFilter = {
|
|
69
70
|
$and: [
|
|
70
71
|
{ population: { $gte: 1000000 } },
|
|
71
72
|
{ 'geography.continent': 'Asia' },
|
|
@@ -154,7 +155,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
154
155
|
|
|
155
156
|
describe('complex scenarios', () => {
|
|
156
157
|
it('deeply nested logical operators', () => {
|
|
157
|
-
const filter = {
|
|
158
|
+
const filter: UpstashVectorFilter = {
|
|
158
159
|
$or: [
|
|
159
160
|
{
|
|
160
161
|
$and: [
|
|
@@ -214,7 +215,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
214
215
|
});
|
|
215
216
|
|
|
216
217
|
it('complex filtering with all operator types', () => {
|
|
217
|
-
const filter = {
|
|
218
|
+
const filter: UpstashVectorFilter = {
|
|
218
219
|
$and: [
|
|
219
220
|
{
|
|
220
221
|
$or: [{ name: { $regex: 'San*' } }, { name: { $regex: 'New*' } }],
|
|
@@ -531,7 +532,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
531
532
|
});
|
|
532
533
|
|
|
533
534
|
it('throws error for invalid $not operator', () => {
|
|
534
|
-
expect(() => translator.translate({ field: { $not: true } })).toThrow();
|
|
535
|
+
expect(() => translator.translate({ field: { $not: true } } as any)).toThrow();
|
|
535
536
|
});
|
|
536
537
|
|
|
537
538
|
it('throws error for regex operators', () => {
|
|
@@ -539,7 +540,7 @@ describe('UpstashFilterTranslator', () => {
|
|
|
539
540
|
expect(() => translator.translate(filter)).toThrow();
|
|
540
541
|
});
|
|
541
542
|
it('throws error for non-logical operators at top level', () => {
|
|
542
|
-
const invalidFilters = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
|
|
543
|
+
const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
|
|
543
544
|
|
|
544
545
|
invalidFilters.forEach(filter => {
|
|
545
546
|
expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OperatorSupport, VectorFilter, OperatorValueMap } from '@mastra/core/vector/filter';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type UpstashOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch'> & {
|
|
5
|
+
$contains: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type UpstashVectorFilter = VectorFilter<keyof UpstashOperatorValueMap, UpstashOperatorValueMap>;
|
|
9
|
+
|
|
10
|
+
export class UpstashFilterTranslator extends BaseFilterTranslator<UpstashVectorFilter, string | undefined> {
|
|
5
11
|
protected override getSupportedOperators(): OperatorSupport {
|
|
6
12
|
return {
|
|
7
13
|
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
@@ -11,13 +17,13 @@ export class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
|
11
17
|
};
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
translate(filter?:
|
|
20
|
+
translate(filter?: UpstashVectorFilter): string | undefined {
|
|
15
21
|
if (this.isEmpty(filter)) return undefined;
|
|
16
22
|
this.validateFilter(filter);
|
|
17
23
|
return this.translateNode(filter);
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
private translateNode(node:
|
|
26
|
+
private translateNode(node: UpstashVectorFilter, path: string = ''): string {
|
|
21
27
|
if (this.isRegex(node)) {
|
|
22
28
|
throw new Error('Direct regex pattern format is not supported in Upstash');
|
|
23
29
|
}
|
package/src/vector/index.test.ts
CHANGED
|
@@ -1146,7 +1146,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
|
|
|
1146
1146
|
vectorStore.query({
|
|
1147
1147
|
indexName: filterIndexName,
|
|
1148
1148
|
queryVector: createVector(0),
|
|
1149
|
-
filter: { field: { $invalidOp: 'value' } },
|
|
1149
|
+
filter: { field: { $invalidOp: 'value' } as any },
|
|
1150
1150
|
}),
|
|
1151
1151
|
).rejects.toThrow();
|
|
1152
1152
|
});
|
|
@@ -1196,7 +1196,7 @@ describe.skipIf(!process.env.UPSTASH_VECTOR_URL || !process.env.UPSTASH_VECTOR_T
|
|
|
1196
1196
|
const results = await vectorStore.query({
|
|
1197
1197
|
indexName: filterIndexName,
|
|
1198
1198
|
queryVector: createVector(0),
|
|
1199
|
-
filter: { $and: { not: 'an array' } },
|
|
1199
|
+
filter: { $and: { not: 'an array' } as any },
|
|
1200
1200
|
});
|
|
1201
1201
|
expect(results.length).toBeGreaterThan(0);
|
|
1202
1202
|
});
|
package/src/vector/index.ts
CHANGED
|
@@ -11,12 +11,14 @@ import type {
|
|
|
11
11
|
UpdateVectorParams,
|
|
12
12
|
UpsertVectorParams,
|
|
13
13
|
} from '@mastra/core/vector';
|
|
14
|
-
import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
15
14
|
import { Index } from '@upstash/vector';
|
|
16
15
|
|
|
17
16
|
import { UpstashFilterTranslator } from './filter';
|
|
17
|
+
import type { UpstashVectorFilter } from './filter';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
type UpstashQueryVectorParams = QueryVectorParams<UpstashVectorFilter>;
|
|
20
|
+
|
|
21
|
+
export class UpstashVector extends MastraVector<UpstashVectorFilter> {
|
|
20
22
|
private client: Index;
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -67,10 +69,10 @@ export class UpstashVector extends MastraVector {
|
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* Transforms a Mastra vector filter into an Upstash-compatible filter string.
|
|
70
|
-
* @param {
|
|
72
|
+
* @param {UpstashVectorFilter} [filter] - The filter to transform.
|
|
71
73
|
* @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
|
|
72
74
|
*/
|
|
73
|
-
transformFilter(filter?:
|
|
75
|
+
transformFilter(filter?: UpstashVectorFilter) {
|
|
74
76
|
const translator = new UpstashFilterTranslator();
|
|
75
77
|
return translator.translate(filter);
|
|
76
78
|
}
|
|
@@ -95,7 +97,7 @@ export class UpstashVector extends MastraVector {
|
|
|
95
97
|
topK = 10,
|
|
96
98
|
filter,
|
|
97
99
|
includeVector = false,
|
|
98
|
-
}:
|
|
100
|
+
}: UpstashQueryVectorParams): Promise<QueryResult[]> {
|
|
99
101
|
try {
|
|
100
102
|
const ns = this.client.namespace(namespace);
|
|
101
103
|
|