@mastra/upstash 0.1.0-alpha.11 → 0.1.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/_tsup-dts-rollup.d.ts +116 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +466 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BaseFilterTranslator } from '@mastra/core/filter';
|
|
2
|
+
import { Filter } from '@mastra/core/filter';
|
|
3
|
+
import { MastraStorage } from '@mastra/core/storage';
|
|
4
|
+
import { MastraVector } from '@mastra/core/vector';
|
|
5
|
+
import { MessageType } from '@mastra/core/memory';
|
|
6
|
+
import { OperatorSupport } from '@mastra/core/filter';
|
|
7
|
+
import type { QueryResult } from '@mastra/core/vector';
|
|
8
|
+
import { StorageColumn } from '@mastra/core/storage';
|
|
9
|
+
import { StorageGetMessagesArg } from '@mastra/core/storage';
|
|
10
|
+
import { StorageThreadType } from '@mastra/core/memory';
|
|
11
|
+
import { TABLE_NAMES } from '@mastra/core/storage';
|
|
12
|
+
import { WorkflowRunState } from '@mastra/core/workflows';
|
|
13
|
+
|
|
14
|
+
declare interface UpstashConfig {
|
|
15
|
+
url: string;
|
|
16
|
+
token: string;
|
|
17
|
+
}
|
|
18
|
+
export { UpstashConfig }
|
|
19
|
+
export { UpstashConfig as UpstashConfig_alias_1 }
|
|
20
|
+
|
|
21
|
+
export declare class UpstashFilterTranslator extends BaseFilterTranslator {
|
|
22
|
+
protected getSupportedOperators(): OperatorSupport;
|
|
23
|
+
translate(filter?: Filter): string | undefined;
|
|
24
|
+
private translateNode;
|
|
25
|
+
private readonly COMPARISON_OPS;
|
|
26
|
+
private translateOperator;
|
|
27
|
+
private readonly NEGATED_OPERATORS;
|
|
28
|
+
private formatNot;
|
|
29
|
+
private formatValue;
|
|
30
|
+
private formatArray;
|
|
31
|
+
private formatComparison;
|
|
32
|
+
private joinConditions;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare class UpstashStore extends MastraStorage {
|
|
36
|
+
private redis;
|
|
37
|
+
constructor(config: UpstashConfig);
|
|
38
|
+
private getKey;
|
|
39
|
+
private ensureDate;
|
|
40
|
+
private serializeDate;
|
|
41
|
+
createTable({ tableName, schema, }: {
|
|
42
|
+
tableName: TABLE_NAMES;
|
|
43
|
+
schema: Record<string, StorageColumn>;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
clearTable({ tableName }: {
|
|
46
|
+
tableName: TABLE_NAMES;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
insert({ tableName, record }: {
|
|
49
|
+
tableName: TABLE_NAMES;
|
|
50
|
+
record: Record<string, any>;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
load<R>({ tableName, keys }: {
|
|
53
|
+
tableName: TABLE_NAMES;
|
|
54
|
+
keys: Record<string, string>;
|
|
55
|
+
}): Promise<R | null>;
|
|
56
|
+
getThreadById({ threadId }: {
|
|
57
|
+
threadId: string;
|
|
58
|
+
}): Promise<StorageThreadType | null>;
|
|
59
|
+
getThreadsByResourceId({ resourceId }: {
|
|
60
|
+
resourceId: string;
|
|
61
|
+
}): Promise<StorageThreadType[]>;
|
|
62
|
+
saveThread({ thread }: {
|
|
63
|
+
thread: StorageThreadType;
|
|
64
|
+
}): Promise<StorageThreadType>;
|
|
65
|
+
updateThread({ id, title, metadata, }: {
|
|
66
|
+
id: string;
|
|
67
|
+
title: string;
|
|
68
|
+
metadata: Record<string, unknown>;
|
|
69
|
+
}): Promise<StorageThreadType>;
|
|
70
|
+
deleteThread({ threadId }: {
|
|
71
|
+
threadId: string;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
private getMessageKey;
|
|
74
|
+
private getThreadMessagesKey;
|
|
75
|
+
saveMessages({ messages }: {
|
|
76
|
+
messages: MessageType[];
|
|
77
|
+
}): Promise<MessageType[]>;
|
|
78
|
+
getMessages<T = unknown>({ threadId, selectBy }: StorageGetMessagesArg): Promise<T[]>;
|
|
79
|
+
persistWorkflowSnapshot(params: {
|
|
80
|
+
namespace: string;
|
|
81
|
+
workflowName: string;
|
|
82
|
+
runId: string;
|
|
83
|
+
snapshot: WorkflowRunState;
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
loadWorkflowSnapshot(params: {
|
|
86
|
+
namespace: string;
|
|
87
|
+
workflowName: string;
|
|
88
|
+
runId: string;
|
|
89
|
+
}): Promise<WorkflowRunState | null>;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export { UpstashStore }
|
|
93
|
+
export { UpstashStore as UpstashStore_alias_1 }
|
|
94
|
+
|
|
95
|
+
declare class UpstashVector extends MastraVector {
|
|
96
|
+
private client;
|
|
97
|
+
constructor({ url, token }: {
|
|
98
|
+
url: string;
|
|
99
|
+
token: string;
|
|
100
|
+
});
|
|
101
|
+
upsert(indexName: string, vectors: number[][], metadata?: Record<string, any>[], ids?: string[]): Promise<string[]>;
|
|
102
|
+
transformFilter(filter?: Filter): string | undefined;
|
|
103
|
+
createIndex(_indexName: string, _dimension: number, _metric?: 'cosine' | 'euclidean' | 'dotproduct'): Promise<void>;
|
|
104
|
+
query(indexName: string, queryVector: number[], topK?: number, filter?: Filter, includeVector?: boolean): Promise<QueryResult[]>;
|
|
105
|
+
listIndexes(): Promise<string[]>;
|
|
106
|
+
describeIndex(indexName: string): Promise<{
|
|
107
|
+
dimension: number;
|
|
108
|
+
count: number;
|
|
109
|
+
metric: "cosine" | "euclidean" | "dotproduct";
|
|
110
|
+
}>;
|
|
111
|
+
deleteIndex(indexName: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
export { UpstashVector }
|
|
114
|
+
export { UpstashVector as UpstashVector_alias_1 }
|
|
115
|
+
|
|
116
|
+
export { }
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import '@mastra/core/memory';
|
|
2
|
+
import { MastraStorage } from '@mastra/core/storage';
|
|
3
|
+
import '@mastra/core/workflows';
|
|
4
|
+
import { Redis } from '@upstash/redis';
|
|
5
|
+
import { MastraVector } from '@mastra/core/vector';
|
|
6
|
+
import { Index } from '@upstash/vector';
|
|
7
|
+
import { BaseFilterTranslator } from '@mastra/core/filter';
|
|
8
|
+
|
|
9
|
+
// src/storage/index.ts
|
|
10
|
+
var UpstashStore = class extends MastraStorage {
|
|
11
|
+
redis;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super({ name: "Upstash" });
|
|
14
|
+
this.redis = new Redis({
|
|
15
|
+
url: config.url,
|
|
16
|
+
token: config.token
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
getKey(tableName, keys) {
|
|
20
|
+
const keyParts = Object.entries(keys).map(([key, value]) => `${key}:${value}`);
|
|
21
|
+
return `${tableName}:${keyParts.join(":")}`;
|
|
22
|
+
}
|
|
23
|
+
ensureDate(date) {
|
|
24
|
+
if (!date) return undefined;
|
|
25
|
+
return date instanceof Date ? date : new Date(date);
|
|
26
|
+
}
|
|
27
|
+
serializeDate(date) {
|
|
28
|
+
if (!date) return undefined;
|
|
29
|
+
const dateObj = this.ensureDate(date);
|
|
30
|
+
return dateObj?.toISOString();
|
|
31
|
+
}
|
|
32
|
+
async createTable({
|
|
33
|
+
tableName,
|
|
34
|
+
schema
|
|
35
|
+
}) {
|
|
36
|
+
await this.redis.set(`schema:${tableName}`, schema);
|
|
37
|
+
}
|
|
38
|
+
async clearTable({ tableName }) {
|
|
39
|
+
const pattern = `${tableName}:*`;
|
|
40
|
+
const keys = await this.redis.keys(pattern);
|
|
41
|
+
if (keys.length > 0) {
|
|
42
|
+
await this.redis.del(...keys);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async insert({ tableName, record }) {
|
|
46
|
+
let key;
|
|
47
|
+
if (tableName === MastraStorage.TABLE_MESSAGES) {
|
|
48
|
+
key = this.getKey(tableName, { threadId: record.threadId, id: record.id });
|
|
49
|
+
} else {
|
|
50
|
+
key = this.getKey(tableName, { id: record.id });
|
|
51
|
+
}
|
|
52
|
+
const processedRecord = {
|
|
53
|
+
...record,
|
|
54
|
+
createdAt: this.serializeDate(record.createdAt),
|
|
55
|
+
updatedAt: this.serializeDate(record.updatedAt)
|
|
56
|
+
};
|
|
57
|
+
await this.redis.set(key, processedRecord);
|
|
58
|
+
}
|
|
59
|
+
async load({ tableName, keys }) {
|
|
60
|
+
const key = this.getKey(tableName, keys);
|
|
61
|
+
const data = await this.redis.get(key);
|
|
62
|
+
return data || null;
|
|
63
|
+
}
|
|
64
|
+
async getThreadById({ threadId }) {
|
|
65
|
+
const thread = await this.load({
|
|
66
|
+
tableName: MastraStorage.TABLE_THREADS,
|
|
67
|
+
keys: { id: threadId }
|
|
68
|
+
});
|
|
69
|
+
if (!thread) return null;
|
|
70
|
+
return {
|
|
71
|
+
...thread,
|
|
72
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
73
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
74
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async getThreadsByResourceId({ resourceId }) {
|
|
78
|
+
const pattern = `${MastraStorage.TABLE_THREADS}:*`;
|
|
79
|
+
const keys = await this.redis.keys(pattern);
|
|
80
|
+
const threads = await Promise.all(
|
|
81
|
+
keys.map(async (key) => {
|
|
82
|
+
const data = await this.redis.get(key);
|
|
83
|
+
return data;
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
return threads.filter((thread) => thread && thread.resourceId === resourceId).map((thread) => ({
|
|
87
|
+
...thread,
|
|
88
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
89
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
90
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
async saveThread({ thread }) {
|
|
94
|
+
await this.insert({
|
|
95
|
+
tableName: MastraStorage.TABLE_THREADS,
|
|
96
|
+
record: thread
|
|
97
|
+
});
|
|
98
|
+
return thread;
|
|
99
|
+
}
|
|
100
|
+
async updateThread({
|
|
101
|
+
id,
|
|
102
|
+
title,
|
|
103
|
+
metadata
|
|
104
|
+
}) {
|
|
105
|
+
const thread = await this.getThreadById({ threadId: id });
|
|
106
|
+
if (!thread) {
|
|
107
|
+
throw new Error(`Thread ${id} not found`);
|
|
108
|
+
}
|
|
109
|
+
const updatedThread = {
|
|
110
|
+
...thread,
|
|
111
|
+
title,
|
|
112
|
+
metadata: {
|
|
113
|
+
...thread.metadata,
|
|
114
|
+
...metadata
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
await this.__saveThread({ thread: updatedThread });
|
|
118
|
+
return updatedThread;
|
|
119
|
+
}
|
|
120
|
+
async deleteThread({ threadId }) {
|
|
121
|
+
const key = this.getKey(MastraStorage.TABLE_THREADS, { id: threadId });
|
|
122
|
+
await this.redis.del(key);
|
|
123
|
+
}
|
|
124
|
+
getMessageKey(threadId, messageId) {
|
|
125
|
+
return this.getKey(MastraStorage.TABLE_MESSAGES, { threadId, id: messageId });
|
|
126
|
+
}
|
|
127
|
+
getThreadMessagesKey(threadId) {
|
|
128
|
+
return `thread:${threadId}:messages`;
|
|
129
|
+
}
|
|
130
|
+
async saveMessages({ messages }) {
|
|
131
|
+
if (messages.length === 0) return [];
|
|
132
|
+
const pipeline = this.redis.pipeline();
|
|
133
|
+
const messagesWithIndex = messages.map((message, index) => ({
|
|
134
|
+
...message,
|
|
135
|
+
_index: index
|
|
136
|
+
}));
|
|
137
|
+
for (const message of messagesWithIndex) {
|
|
138
|
+
const key = this.getMessageKey(message.threadId, message.id);
|
|
139
|
+
const score = message._index !== undefined ? message._index : new Date(message.createdAt).getTime();
|
|
140
|
+
pipeline.set(key, message);
|
|
141
|
+
pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
|
|
142
|
+
score,
|
|
143
|
+
member: message.id
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
await pipeline.exec();
|
|
147
|
+
return messages;
|
|
148
|
+
}
|
|
149
|
+
async getMessages({ threadId, selectBy }) {
|
|
150
|
+
const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
|
|
151
|
+
const messageIds = /* @__PURE__ */ new Set();
|
|
152
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
153
|
+
if (limit === 0 && !selectBy?.include) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
if (selectBy?.include?.length) {
|
|
157
|
+
for (const item of selectBy.include) {
|
|
158
|
+
messageIds.add(item.id);
|
|
159
|
+
if (item.withPreviousMessages || item.withNextMessages) {
|
|
160
|
+
const rank = await this.redis.zrank(threadMessagesKey, item.id);
|
|
161
|
+
if (rank === null) continue;
|
|
162
|
+
if (item.withPreviousMessages) {
|
|
163
|
+
const start = Math.max(0, rank - item.withPreviousMessages);
|
|
164
|
+
const prevIds = rank === 0 ? [] : await this.redis.zrange(threadMessagesKey, start, rank - 1);
|
|
165
|
+
prevIds.forEach((id) => messageIds.add(id));
|
|
166
|
+
}
|
|
167
|
+
if (item.withNextMessages) {
|
|
168
|
+
const nextIds = await this.redis.zrange(threadMessagesKey, rank + 1, rank + item.withNextMessages);
|
|
169
|
+
nextIds.forEach((id) => messageIds.add(id));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const latestIds = limit === 0 ? [] : await this.redis.zrange(threadMessagesKey, -limit, -1);
|
|
175
|
+
latestIds.forEach((id) => messageIds.add(id));
|
|
176
|
+
const messages = (await Promise.all(
|
|
177
|
+
Array.from(messageIds).map(
|
|
178
|
+
async (id) => this.redis.get(this.getMessageKey(threadId, id))
|
|
179
|
+
)
|
|
180
|
+
)).filter((msg) => msg !== null);
|
|
181
|
+
const messageOrder = await this.redis.zrange(threadMessagesKey, 0, -1);
|
|
182
|
+
messages.sort((a, b) => messageOrder.indexOf(a.id) - messageOrder.indexOf(b.id));
|
|
183
|
+
return messages.map(({ _index, ...message }) => message);
|
|
184
|
+
}
|
|
185
|
+
async persistWorkflowSnapshot(params) {
|
|
186
|
+
const { namespace, workflowName, runId, snapshot } = params;
|
|
187
|
+
const key = this.getKey(MastraStorage.TABLE_WORKFLOW_SNAPSHOT, {
|
|
188
|
+
namespace,
|
|
189
|
+
workflow_name: workflowName,
|
|
190
|
+
run_id: runId
|
|
191
|
+
});
|
|
192
|
+
await this.redis.set(key, snapshot);
|
|
193
|
+
}
|
|
194
|
+
async loadWorkflowSnapshot(params) {
|
|
195
|
+
const { namespace, workflowName, runId } = params;
|
|
196
|
+
const key = this.getKey(MastraStorage.TABLE_WORKFLOW_SNAPSHOT, {
|
|
197
|
+
namespace,
|
|
198
|
+
workflow_name: workflowName,
|
|
199
|
+
run_id: runId
|
|
200
|
+
});
|
|
201
|
+
const data = await this.redis.get(key);
|
|
202
|
+
return data || null;
|
|
203
|
+
}
|
|
204
|
+
async close() {
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var UpstashFilterTranslator = class extends BaseFilterTranslator {
|
|
208
|
+
getSupportedOperators() {
|
|
209
|
+
return {
|
|
210
|
+
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
211
|
+
array: ["$in", "$nin", "$all"],
|
|
212
|
+
regex: ["$regex"],
|
|
213
|
+
custom: ["$contains"]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
translate(filter) {
|
|
217
|
+
if (this.isEmpty(filter)) return undefined;
|
|
218
|
+
this.validateFilter(filter);
|
|
219
|
+
return this.translateNode(filter);
|
|
220
|
+
}
|
|
221
|
+
translateNode(node, path = "") {
|
|
222
|
+
if (this.isRegex(node)) {
|
|
223
|
+
throw new Error("Direct regex pattern format is not supported in Upstash");
|
|
224
|
+
}
|
|
225
|
+
if (node === null || node === undefined) {
|
|
226
|
+
throw new Error("Filtering for null/undefined values is not supported by Upstash Vector");
|
|
227
|
+
}
|
|
228
|
+
if (this.isPrimitive(node)) {
|
|
229
|
+
if (node === null || node === undefined) {
|
|
230
|
+
throw new Error("Filtering for null/undefined values is not supported by Upstash Vector");
|
|
231
|
+
}
|
|
232
|
+
return this.formatComparison(path, "=", node);
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(node)) {
|
|
235
|
+
if (node.length === 0) {
|
|
236
|
+
return "(HAS FIELD empty AND HAS NOT FIELD empty)";
|
|
237
|
+
}
|
|
238
|
+
return `${path} IN (${this.formatArray(node)})`;
|
|
239
|
+
}
|
|
240
|
+
const entries = Object.entries(node);
|
|
241
|
+
const conditions = [];
|
|
242
|
+
for (const [key, value] of entries) {
|
|
243
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
244
|
+
if (this.isOperator(key)) {
|
|
245
|
+
conditions.push(this.translateOperator(key, value, path));
|
|
246
|
+
} else if (typeof value === "object" && value !== null) {
|
|
247
|
+
conditions.push(this.translateNode(value, newPath));
|
|
248
|
+
} else if (value === null || value === undefined) {
|
|
249
|
+
throw new Error("Filtering for null/undefined values is not supported by Upstash Vector");
|
|
250
|
+
} else {
|
|
251
|
+
conditions.push(this.formatComparison(newPath, "=", value));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return conditions.length > 1 ? `(${conditions.join(" AND ")})` : conditions[0] ?? "";
|
|
255
|
+
}
|
|
256
|
+
COMPARISON_OPS = {
|
|
257
|
+
$eq: "=",
|
|
258
|
+
$ne: "!=",
|
|
259
|
+
$gt: ">",
|
|
260
|
+
$gte: ">=",
|
|
261
|
+
$lt: "<",
|
|
262
|
+
$lte: "<="
|
|
263
|
+
};
|
|
264
|
+
translateOperator(operator, value, path) {
|
|
265
|
+
if (this.isBasicOperator(operator) || this.isNumericOperator(operator)) {
|
|
266
|
+
return this.formatComparison(path, this.COMPARISON_OPS[operator], value);
|
|
267
|
+
}
|
|
268
|
+
switch (operator) {
|
|
269
|
+
case "$in":
|
|
270
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
271
|
+
return "(HAS FIELD empty AND HAS NOT FIELD empty)";
|
|
272
|
+
}
|
|
273
|
+
return `${path} IN (${this.formatArray(value)})`;
|
|
274
|
+
case "$nin":
|
|
275
|
+
return `${path} NOT IN (${this.formatArray(value)})`;
|
|
276
|
+
case "$contains":
|
|
277
|
+
return `${path} CONTAINS ${this.formatValue(value)}`;
|
|
278
|
+
case "$regex":
|
|
279
|
+
return `${path} GLOB ${this.formatValue(value)}`;
|
|
280
|
+
case "$exists":
|
|
281
|
+
return value ? `HAS FIELD ${path}` : `HAS NOT FIELD ${path}`;
|
|
282
|
+
case "$and":
|
|
283
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
284
|
+
return "(HAS FIELD empty OR HAS NOT FIELD empty)";
|
|
285
|
+
}
|
|
286
|
+
return this.joinConditions(value, "AND");
|
|
287
|
+
case "$or":
|
|
288
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
289
|
+
return "(HAS FIELD empty AND HAS NOT FIELD empty)";
|
|
290
|
+
}
|
|
291
|
+
return this.joinConditions(value, "OR");
|
|
292
|
+
case "$not":
|
|
293
|
+
return this.formatNot(path, value);
|
|
294
|
+
case "$nor":
|
|
295
|
+
return this.formatNot("", { $or: value });
|
|
296
|
+
case "$all":
|
|
297
|
+
return this.translateOperator(
|
|
298
|
+
"$and",
|
|
299
|
+
value.map((item) => ({ [path]: { $contains: item } })),
|
|
300
|
+
""
|
|
301
|
+
);
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
NEGATED_OPERATORS = {
|
|
307
|
+
$eq: "$ne",
|
|
308
|
+
$ne: "$eq",
|
|
309
|
+
$gt: "$lte",
|
|
310
|
+
$gte: "$lt",
|
|
311
|
+
$lt: "$gte",
|
|
312
|
+
$lte: "$gt",
|
|
313
|
+
$in: "$nin",
|
|
314
|
+
$nin: "$in",
|
|
315
|
+
$exists: "$exists"
|
|
316
|
+
// Special case - we'll flip the value
|
|
317
|
+
};
|
|
318
|
+
formatNot(path, value) {
|
|
319
|
+
if (typeof value !== "object") {
|
|
320
|
+
return `${path} != ${this.formatValue(value)}`;
|
|
321
|
+
}
|
|
322
|
+
if (!Object.keys(value).some((k) => k.startsWith("$"))) {
|
|
323
|
+
const [fieldName, fieldValue] = Object.entries(value)[0] ?? [];
|
|
324
|
+
if (typeof fieldValue === "object" && fieldValue !== null && Object.keys(fieldValue)[0]?.startsWith("$")) {
|
|
325
|
+
const [op2, val2] = Object.entries(fieldValue)[0] ?? [];
|
|
326
|
+
const negatedOp = this.NEGATED_OPERATORS[op2];
|
|
327
|
+
if (!negatedOp) throw new Error(`Unsupported operator in NOT: ${op2}`);
|
|
328
|
+
if (op2 === "$exists") {
|
|
329
|
+
return this.translateOperator(op2, !val2, fieldName ?? "");
|
|
330
|
+
}
|
|
331
|
+
return this.translateOperator(negatedOp, val2, fieldName ?? "");
|
|
332
|
+
}
|
|
333
|
+
return `${fieldName} != ${this.formatValue(fieldValue)}`;
|
|
334
|
+
}
|
|
335
|
+
const [op, val] = Object.entries(value)[0] ?? [];
|
|
336
|
+
if (op === "$lt") return `${path} >= ${this.formatValue(val)}`;
|
|
337
|
+
if (op === "$lte") return `${path} > ${this.formatValue(val)}`;
|
|
338
|
+
if (op === "$gt") return `${path} <= ${this.formatValue(val)}`;
|
|
339
|
+
if (op === "$gte") return `${path} < ${this.formatValue(val)}`;
|
|
340
|
+
if (op === "$ne") return `${path} = ${this.formatValue(val)}`;
|
|
341
|
+
if (op === "$eq") return `${path} != ${this.formatValue(val)}`;
|
|
342
|
+
if (op === "$contains") return `${path} NOT CONTAINS ${this.formatValue(val)}`;
|
|
343
|
+
if (op === "$regex") return `${path} NOT GLOB ${this.formatValue(val)}`;
|
|
344
|
+
if (op === "$in") return `${path} NOT IN (${this.formatArray(val)})`;
|
|
345
|
+
if (op === "$exists") return val ? `HAS NOT FIELD ${path}` : `HAS FIELD ${path}`;
|
|
346
|
+
if (op === "$and" || op === "$or") {
|
|
347
|
+
const newOp = op === "$and" ? "$or" : "$and";
|
|
348
|
+
const conditions = val.map((condition) => {
|
|
349
|
+
const [fieldName, fieldValue] = Object.entries(condition)[0] ?? [];
|
|
350
|
+
return { [fieldName]: { $not: fieldValue } };
|
|
351
|
+
});
|
|
352
|
+
return this.translateOperator(newOp, conditions, "");
|
|
353
|
+
}
|
|
354
|
+
if (op === "$nor") {
|
|
355
|
+
return this.translateOperator("$or", val, "");
|
|
356
|
+
}
|
|
357
|
+
return `${path} != ${this.formatValue(val)}`;
|
|
358
|
+
}
|
|
359
|
+
formatValue(value) {
|
|
360
|
+
if (value === null || value === undefined) {
|
|
361
|
+
throw new Error("Filtering for null/undefined values is not supported by Upstash Vector");
|
|
362
|
+
}
|
|
363
|
+
if (typeof value === "string") {
|
|
364
|
+
const hasSingleQuote = /'/g.test(value);
|
|
365
|
+
const hasDoubleQuote = /"/g.test(value);
|
|
366
|
+
if (hasSingleQuote && hasDoubleQuote) {
|
|
367
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
368
|
+
}
|
|
369
|
+
if (hasSingleQuote) {
|
|
370
|
+
return `"${value}"`;
|
|
371
|
+
}
|
|
372
|
+
return `'${value}'`;
|
|
373
|
+
}
|
|
374
|
+
if (typeof value === "number") {
|
|
375
|
+
if (Math.abs(value) < 1e-6 || Math.abs(value) > 1e6) {
|
|
376
|
+
return value.toFixed(20).replace(/\.?0+$/, "");
|
|
377
|
+
}
|
|
378
|
+
return value.toString();
|
|
379
|
+
}
|
|
380
|
+
return String(value);
|
|
381
|
+
}
|
|
382
|
+
formatArray(values) {
|
|
383
|
+
return values.map((value) => {
|
|
384
|
+
if (value === null || value === undefined) {
|
|
385
|
+
throw new Error("Filtering for null/undefined values is not supported by Upstash Vector");
|
|
386
|
+
}
|
|
387
|
+
return this.formatValue(value);
|
|
388
|
+
}).join(", ");
|
|
389
|
+
}
|
|
390
|
+
formatComparison(path, op, value) {
|
|
391
|
+
return `${path} ${op} ${this.formatValue(value)}`;
|
|
392
|
+
}
|
|
393
|
+
joinConditions(conditions, operator) {
|
|
394
|
+
const translated = Array.isArray(conditions) ? conditions.map((c) => this.translateNode(c)) : [this.translateNode(conditions)];
|
|
395
|
+
return `(${translated.join(` ${operator} `)})`;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// src/vector/index.ts
|
|
400
|
+
var UpstashVector = class extends MastraVector {
|
|
401
|
+
client;
|
|
402
|
+
constructor({ url, token }) {
|
|
403
|
+
super();
|
|
404
|
+
this.client = new Index({
|
|
405
|
+
url,
|
|
406
|
+
token
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
async upsert(indexName, vectors, metadata, ids) {
|
|
410
|
+
const generatedIds = ids || vectors.map(() => crypto.randomUUID());
|
|
411
|
+
const points = vectors.map((vector, index) => ({
|
|
412
|
+
id: generatedIds[index],
|
|
413
|
+
vector,
|
|
414
|
+
metadata: metadata?.[index]
|
|
415
|
+
}));
|
|
416
|
+
await this.client.upsert(points, {
|
|
417
|
+
namespace: indexName
|
|
418
|
+
});
|
|
419
|
+
return generatedIds;
|
|
420
|
+
}
|
|
421
|
+
transformFilter(filter) {
|
|
422
|
+
const translator = new UpstashFilterTranslator();
|
|
423
|
+
return translator.translate(filter);
|
|
424
|
+
}
|
|
425
|
+
async createIndex(_indexName, _dimension, _metric = "cosine") {
|
|
426
|
+
console.log("No need to call createIndex for Upstash");
|
|
427
|
+
}
|
|
428
|
+
async query(indexName, queryVector, topK = 10, filter, includeVector = false) {
|
|
429
|
+
const ns = this.client.namespace(indexName);
|
|
430
|
+
const filterString = this.transformFilter(filter);
|
|
431
|
+
const results = await ns.query({
|
|
432
|
+
topK,
|
|
433
|
+
vector: queryVector,
|
|
434
|
+
includeVectors: includeVector,
|
|
435
|
+
includeMetadata: true,
|
|
436
|
+
...filterString ? { filter: filterString } : {}
|
|
437
|
+
});
|
|
438
|
+
return (results || []).map((result) => ({
|
|
439
|
+
id: `${result.id}`,
|
|
440
|
+
score: result.score,
|
|
441
|
+
metadata: result.metadata,
|
|
442
|
+
...includeVector && { vector: result.vector || [] }
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
async listIndexes() {
|
|
446
|
+
const indexes = await this.client.listNamespaces();
|
|
447
|
+
return indexes.filter(Boolean);
|
|
448
|
+
}
|
|
449
|
+
async describeIndex(indexName) {
|
|
450
|
+
const info = await this.client.info();
|
|
451
|
+
return {
|
|
452
|
+
dimension: info.dimension,
|
|
453
|
+
count: info.namespaces?.[indexName]?.vectorCount || 0,
|
|
454
|
+
metric: info?.similarityFunction?.toLowerCase()
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async deleteIndex(indexName) {
|
|
458
|
+
try {
|
|
459
|
+
await this.client.deleteNamespace(indexName);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error("Failed to delete namespace:", error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export { UpstashStore, UpstashVector };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/upstash",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.12",
|
|
4
4
|
"description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@upstash/redis": "^1.28.3",
|
|
19
19
|
"@upstash/vector": "^1.1.7",
|
|
20
|
-
"@mastra/core": "^0.2.0-alpha.
|
|
20
|
+
"@mastra/core": "^0.2.0-alpha.102"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@microsoft/api-extractor": "^7.49.2",
|