@mastra/cloudflare 0.0.0-vnext-inngest-20250508131921 → 0.0.0-vnext-20251104230439
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 +1536 -0
- package/LICENSE.md +12 -4
- package/README.md +1 -2
- package/dist/index.cjs +1995 -812
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1983 -800
- package/dist/index.js.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +87 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -0
- package/dist/storage/domains/operations/index.d.ts +82 -0
- package/dist/storage/domains/operations/index.d.ts.map +1 -0
- package/dist/storage/domains/scores/index.d.ts +50 -0
- package/dist/storage/domains/scores/index.d.ts.map +1 -0
- package/dist/storage/domains/workflows/index.d.ts +47 -0
- package/dist/storage/domains/workflows/index.d.ts.map +1 -0
- package/dist/storage/index.d.ts +175 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/test-utils.d.ts +25 -0
- package/dist/storage/test-utils.d.ts.map +1 -0
- package/dist/storage/types.d.ts +66 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/package.json +33 -18
- package/dist/_tsup-dts-rollup.d.cts +0 -276
- package/dist/_tsup-dts-rollup.d.ts +0 -276
- package/dist/index.d.cts +0 -1
package/dist/index.js
CHANGED
|
@@ -1,327 +1,436 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
+
import { MastraStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCORERS, StoreOperations, TABLE_TRACES, WorkflowsStorage, ensureDate, normalizePerPage, MemoryStorage, calculatePagination, serializeDate, resolveMessageLimit, TABLE_RESOURCES, ScoresStorage, safelyParseJSON } from '@mastra/core/storage';
|
|
2
3
|
import Cloudflare from 'cloudflare';
|
|
4
|
+
import { MessageList } from '@mastra/core/agent';
|
|
5
|
+
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
3
6
|
|
|
4
7
|
// src/storage/index.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// src/storage/index.ts
|
|
12
|
-
var CloudflareStore = class extends MastraStorage {
|
|
13
|
-
client;
|
|
14
|
-
accountId;
|
|
15
|
-
namespacePrefix;
|
|
16
|
-
bindings;
|
|
17
|
-
validateWorkersConfig(config) {
|
|
18
|
-
if (!isWorkersConfig(config)) {
|
|
19
|
-
throw new Error("Invalid Workers API configuration");
|
|
20
|
-
}
|
|
21
|
-
if (!config.bindings) {
|
|
22
|
-
throw new Error("KV bindings are required when using Workers Binding API");
|
|
23
|
-
}
|
|
24
|
-
const requiredTables = [TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_TRACES];
|
|
25
|
-
for (const table of requiredTables) {
|
|
26
|
-
if (!(table in config.bindings)) {
|
|
27
|
-
throw new Error(`Missing KV binding for table: ${table}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
8
|
+
var MemoryStorageCloudflare = class extends MemoryStorage {
|
|
9
|
+
operations;
|
|
10
|
+
constructor({ operations }) {
|
|
11
|
+
super();
|
|
12
|
+
this.operations = operations;
|
|
30
13
|
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
if (!config.accountId?.trim()) {
|
|
36
|
-
throw new Error("accountId is required for REST API");
|
|
37
|
-
}
|
|
38
|
-
if (!config.apiToken?.trim()) {
|
|
39
|
-
throw new Error("apiToken is required for REST API");
|
|
40
|
-
}
|
|
14
|
+
ensureMetadata(metadata) {
|
|
15
|
+
if (!metadata) return void 0;
|
|
16
|
+
return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
|
|
41
17
|
}
|
|
42
|
-
|
|
43
|
-
|
|
18
|
+
async getThreadById({ threadId }) {
|
|
19
|
+
const thread = await this.operations.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
|
|
20
|
+
if (!thread) return null;
|
|
44
21
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
}
|
|
51
|
-
this.validateRestConfig(config);
|
|
52
|
-
this.accountId = config.accountId.trim();
|
|
53
|
-
this.namespacePrefix = config.namespacePrefix?.trim() || "";
|
|
54
|
-
this.client = new Cloudflare({
|
|
55
|
-
apiToken: config.apiToken.trim()
|
|
56
|
-
});
|
|
57
|
-
this.logger.info("Using Cloudflare KV REST API");
|
|
58
|
-
}
|
|
22
|
+
return {
|
|
23
|
+
...thread,
|
|
24
|
+
createdAt: ensureDate(thread.createdAt),
|
|
25
|
+
updatedAt: ensureDate(thread.updatedAt),
|
|
26
|
+
metadata: this.ensureMetadata(thread.metadata)
|
|
27
|
+
};
|
|
59
28
|
} catch (error) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
29
|
+
const mastraError = new MastraError(
|
|
30
|
+
{
|
|
31
|
+
id: "CLOUDFLARE_STORAGE_GET_THREAD_BY_ID_FAILED",
|
|
32
|
+
domain: ErrorDomain.STORAGE,
|
|
33
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
34
|
+
details: {
|
|
35
|
+
threadId
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
error
|
|
39
|
+
);
|
|
40
|
+
this.logger?.trackException(mastraError);
|
|
41
|
+
this.logger?.error(mastraError.toString());
|
|
42
|
+
return null;
|
|
68
43
|
}
|
|
69
|
-
const binding = this.bindings[tableName];
|
|
70
|
-
if (!binding) throw new Error(`No binding found for namespace ${tableName}`);
|
|
71
|
-
return binding;
|
|
72
44
|
}
|
|
73
|
-
async
|
|
74
|
-
|
|
45
|
+
async listThreadsByResourceId(args) {
|
|
46
|
+
try {
|
|
47
|
+
const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
|
|
48
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
49
|
+
if (page < 0) {
|
|
50
|
+
throw new MastraError(
|
|
51
|
+
{
|
|
52
|
+
id: "STORAGE_CLOUDFLARE_LIST_THREADS_BY_RESOURCE_ID_INVALID_PAGE",
|
|
53
|
+
domain: ErrorDomain.STORAGE,
|
|
54
|
+
category: ErrorCategory.USER,
|
|
55
|
+
details: { page }
|
|
56
|
+
},
|
|
57
|
+
new Error("page must be >= 0")
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
61
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
62
|
+
const prefix = this.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
|
|
63
|
+
const keyObjs = await this.operations.listKV(TABLE_THREADS, { prefix: `${prefix}${TABLE_THREADS}` });
|
|
64
|
+
const threads = [];
|
|
65
|
+
for (const { name: key } of keyObjs) {
|
|
66
|
+
const data = await this.operations.getKV(TABLE_THREADS, key);
|
|
67
|
+
if (!data) continue;
|
|
68
|
+
if (data.resourceId !== resourceId) continue;
|
|
69
|
+
threads.push(data);
|
|
70
|
+
}
|
|
71
|
+
threads.sort((a, b) => {
|
|
72
|
+
const aTime = new Date(a[field] || 0).getTime();
|
|
73
|
+
const bTime = new Date(b[field] || 0).getTime();
|
|
74
|
+
return direction === "ASC" ? aTime - bTime : bTime - aTime;
|
|
75
|
+
});
|
|
76
|
+
const end = perPageInput === false ? threads.length : offset + perPage;
|
|
77
|
+
const paginatedThreads = threads.slice(offset, end);
|
|
75
78
|
return {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
page,
|
|
80
|
+
perPage: perPageForResponse,
|
|
81
|
+
total: threads.length,
|
|
82
|
+
hasMore: perPageInput === false ? false : offset + perPage < threads.length,
|
|
83
|
+
threads: paginatedThreads
|
|
81
84
|
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new MastraError(
|
|
87
|
+
{
|
|
88
|
+
id: "CLOUDFLARE_STORAGE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
|
|
89
|
+
domain: ErrorDomain.STORAGE,
|
|
90
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
91
|
+
text: "Failed to get threads by resource ID with pagination"
|
|
92
|
+
},
|
|
93
|
+
error
|
|
94
|
+
);
|
|
82
95
|
}
|
|
83
|
-
return await this.client.kv.namespaces.list({ account_id: this.accountId });
|
|
84
96
|
}
|
|
85
|
-
async
|
|
97
|
+
async saveThread({ thread }) {
|
|
86
98
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const result = await binding.getWithMetadata(key, "text");
|
|
90
|
-
if (!result) return null;
|
|
91
|
-
return JSON.stringify(result);
|
|
92
|
-
} else {
|
|
93
|
-
const namespaceId = await this.getNamespaceId(tableName);
|
|
94
|
-
const response = await this.client.kv.namespaces.values.get(namespaceId, key, {
|
|
95
|
-
account_id: this.accountId
|
|
96
|
-
});
|
|
97
|
-
return await response.text();
|
|
98
|
-
}
|
|
99
|
+
await this.operations.insert({ tableName: TABLE_THREADS, record: thread });
|
|
100
|
+
return thread;
|
|
99
101
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
throw new MastraError(
|
|
103
|
+
{
|
|
104
|
+
id: "CLOUDFLARE_STORAGE_SAVE_THREAD_FAILED",
|
|
105
|
+
domain: ErrorDomain.STORAGE,
|
|
106
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
107
|
+
details: {
|
|
108
|
+
threadId: thread.id
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
error
|
|
112
|
+
);
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
|
-
async
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
value,
|
|
115
|
+
async updateThread({
|
|
116
|
+
id,
|
|
117
|
+
title,
|
|
112
118
|
metadata
|
|
113
119
|
}) {
|
|
114
120
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const binding = this.getBinding(tableName);
|
|
119
|
-
await binding.put(key, serializedValue, { metadata: serializedMetadata });
|
|
120
|
-
} else {
|
|
121
|
-
const namespaceId = await this.getNamespaceId(tableName);
|
|
122
|
-
await this.client.kv.namespaces.values.update(namespaceId, key, {
|
|
123
|
-
account_id: this.accountId,
|
|
124
|
-
value: serializedValue,
|
|
125
|
-
metadata: serializedMetadata
|
|
126
|
-
});
|
|
121
|
+
const thread = await this.getThreadById({ threadId: id });
|
|
122
|
+
if (!thread) {
|
|
123
|
+
throw new Error(`Thread ${id} not found`);
|
|
127
124
|
}
|
|
125
|
+
const updatedThread = {
|
|
126
|
+
...thread,
|
|
127
|
+
title,
|
|
128
|
+
metadata: this.ensureMetadata({
|
|
129
|
+
...thread.metadata ?? {},
|
|
130
|
+
...metadata
|
|
131
|
+
}),
|
|
132
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
133
|
+
};
|
|
134
|
+
await this.operations.insert({ tableName: TABLE_THREADS, record: updatedThread });
|
|
135
|
+
return updatedThread;
|
|
128
136
|
} catch (error) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
account_id: this.accountId
|
|
142
|
-
});
|
|
137
|
+
throw new MastraError(
|
|
138
|
+
{
|
|
139
|
+
id: "CLOUDFLARE_STORAGE_UPDATE_THREAD_FAILED",
|
|
140
|
+
domain: ErrorDomain.STORAGE,
|
|
141
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
142
|
+
details: {
|
|
143
|
+
threadId: id,
|
|
144
|
+
title
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
error
|
|
148
|
+
);
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
|
-
|
|
151
|
+
getMessageKey(threadId, messageId) {
|
|
146
152
|
try {
|
|
147
|
-
|
|
148
|
-
const binding = this.getBinding(tableName);
|
|
149
|
-
const response = await binding.list({
|
|
150
|
-
limit: options?.limit || 1e3,
|
|
151
|
-
prefix: options?.prefix
|
|
152
|
-
});
|
|
153
|
-
return response.keys;
|
|
154
|
-
} else {
|
|
155
|
-
const namespaceId = await this.getNamespaceId(tableName);
|
|
156
|
-
const response = await this.client.kv.namespaces.keys.list(namespaceId, {
|
|
157
|
-
account_id: this.accountId,
|
|
158
|
-
limit: options?.limit || 1e3,
|
|
159
|
-
prefix: options?.prefix
|
|
160
|
-
});
|
|
161
|
-
return response.result;
|
|
162
|
-
}
|
|
153
|
+
return this.operations.getKey(TABLE_MESSAGES, { threadId, id: messageId });
|
|
163
154
|
} catch (error) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
async createNamespaceById(title) {
|
|
169
|
-
if (this.bindings) {
|
|
170
|
-
return {
|
|
171
|
-
id: title,
|
|
172
|
-
// Use title as ID since that's what we need
|
|
173
|
-
title,
|
|
174
|
-
supports_url_encoding: true
|
|
175
|
-
};
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
this.logger.error(`Error getting message key for thread ${threadId} and message ${messageId}:`, { message });
|
|
157
|
+
throw error;
|
|
176
158
|
}
|
|
177
|
-
return await this.client.kv.namespaces.create({
|
|
178
|
-
account_id: this.accountId,
|
|
179
|
-
title
|
|
180
|
-
});
|
|
181
159
|
}
|
|
182
|
-
|
|
160
|
+
getThreadMessagesKey(threadId) {
|
|
183
161
|
try {
|
|
184
|
-
|
|
185
|
-
const namespace = response.result.find((ns) => ns.title === namespaceName);
|
|
186
|
-
return namespace ? namespace.id : null;
|
|
162
|
+
return this.operations.getKey(TABLE_MESSAGES, { threadId, id: "messages" });
|
|
187
163
|
} catch (error) {
|
|
188
|
-
|
|
189
|
-
|
|
164
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
165
|
+
this.logger.error(`Error getting thread messages key for thread ${threadId}:`, { message });
|
|
166
|
+
throw error;
|
|
190
167
|
}
|
|
191
168
|
}
|
|
192
|
-
async
|
|
169
|
+
async deleteThread({ threadId }) {
|
|
193
170
|
try {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (error.message && error.message.includes("already exists")) {
|
|
198
|
-
const namespaces = await this.listNamespaces();
|
|
199
|
-
const namespace = namespaces.result.find((ns) => ns.title === namespaceName);
|
|
200
|
-
if (namespace) return namespace.id;
|
|
171
|
+
const thread = await this.getThreadById({ threadId });
|
|
172
|
+
if (!thread) {
|
|
173
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
201
174
|
}
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
175
|
+
const messageKeys = await this.operations.listKV(TABLE_MESSAGES);
|
|
176
|
+
const threadMessageKeys = messageKeys.filter((key) => key.name.includes(`${TABLE_MESSAGES}:${threadId}:`));
|
|
177
|
+
await Promise.all([
|
|
178
|
+
// Delete message order
|
|
179
|
+
this.operations.deleteKV(TABLE_MESSAGES, this.getThreadMessagesKey(threadId)),
|
|
180
|
+
// Delete all messages
|
|
181
|
+
...threadMessageKeys.map((key) => this.operations.deleteKV(TABLE_MESSAGES, key.name)),
|
|
182
|
+
// Delete thread
|
|
183
|
+
this.operations.deleteKV(TABLE_THREADS, this.operations.getKey(TABLE_THREADS, { id: threadId }))
|
|
184
|
+
]);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw new MastraError(
|
|
187
|
+
{
|
|
188
|
+
id: "CLOUDFLARE_STORAGE_DELETE_THREAD_FAILED",
|
|
189
|
+
domain: ErrorDomain.STORAGE,
|
|
190
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
191
|
+
details: {
|
|
192
|
+
threadId
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
error
|
|
196
|
+
);
|
|
210
197
|
}
|
|
211
|
-
return namespaceId;
|
|
212
198
|
}
|
|
213
|
-
async
|
|
214
|
-
const prefix = this.namespacePrefix ? `${this.namespacePrefix}_` : "";
|
|
199
|
+
async findMessageInAnyThread(messageId) {
|
|
215
200
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
201
|
+
const prefix = this.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
|
|
202
|
+
const threadKeys = await this.operations.listKV(TABLE_THREADS, { prefix: `${prefix}${TABLE_THREADS}` });
|
|
203
|
+
for (const { name: threadKey } of threadKeys) {
|
|
204
|
+
const threadId = threadKey.split(":").pop();
|
|
205
|
+
if (!threadId || threadId === "messages") continue;
|
|
206
|
+
const messageKey = this.getMessageKey(threadId, messageId);
|
|
207
|
+
const message = await this.operations.getKV(TABLE_MESSAGES, messageKey);
|
|
208
|
+
if (message) {
|
|
209
|
+
return { ...message, threadId };
|
|
210
|
+
}
|
|
222
211
|
}
|
|
212
|
+
return null;
|
|
223
213
|
} catch (error) {
|
|
224
|
-
this.logger
|
|
225
|
-
|
|
214
|
+
this.logger?.error(`Error finding message ${messageId} in any thread:`, error);
|
|
215
|
+
return null;
|
|
226
216
|
}
|
|
227
217
|
}
|
|
228
218
|
/**
|
|
229
|
-
*
|
|
219
|
+
* Queue for serializing sorted order updates.
|
|
220
|
+
* Updates the sorted order for a given key. This operation is eventually consistent.
|
|
230
221
|
*/
|
|
231
|
-
|
|
232
|
-
|
|
222
|
+
updateQueue = /* @__PURE__ */ new Map();
|
|
223
|
+
async updateSorting(threadMessages) {
|
|
224
|
+
return threadMessages.map((msg) => ({
|
|
225
|
+
message: msg,
|
|
226
|
+
// Use _index if available, otherwise timestamp, matching Upstash
|
|
227
|
+
score: msg._index !== void 0 ? msg._index : msg.createdAt.getTime()
|
|
228
|
+
})).sort((a, b) => a.score - b.score).map((item) => ({
|
|
229
|
+
id: item.message.id,
|
|
230
|
+
score: item.score
|
|
231
|
+
}));
|
|
233
232
|
}
|
|
234
233
|
/**
|
|
235
|
-
*
|
|
234
|
+
* Updates the sorted order for a given key. This operation is eventually consistent.
|
|
235
|
+
* Note: Operations on the same orderKey are serialized using a queue to prevent
|
|
236
|
+
* concurrent updates from conflicting with each other.
|
|
236
237
|
*/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
238
|
+
async updateSortedMessages(orderKey, newEntries) {
|
|
239
|
+
const currentPromise = this.updateQueue.get(orderKey) || Promise.resolve();
|
|
240
|
+
const nextPromise = currentPromise.then(async () => {
|
|
241
|
+
try {
|
|
242
|
+
const currentOrder = await this.getSortedMessages(orderKey);
|
|
243
|
+
const orderMap = new Map(currentOrder.map((entry) => [entry.id, entry]));
|
|
244
|
+
for (const entry of newEntries) {
|
|
245
|
+
orderMap.set(entry.id, entry);
|
|
246
|
+
}
|
|
247
|
+
const updatedOrder = Array.from(orderMap.values()).sort((a, b) => a.score - b.score);
|
|
248
|
+
await this.operations.putKV({
|
|
249
|
+
tableName: TABLE_MESSAGES,
|
|
250
|
+
key: orderKey,
|
|
251
|
+
value: JSON.stringify(updatedOrder)
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
255
|
+
this.logger.error(`Error updating sorted order for key ${orderKey}:`, { message });
|
|
256
|
+
throw error;
|
|
257
|
+
} finally {
|
|
258
|
+
if (this.updateQueue.get(orderKey) === nextPromise) {
|
|
259
|
+
this.updateQueue.delete(orderKey);
|
|
248
260
|
}
|
|
249
|
-
return null;
|
|
250
261
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
262
|
+
});
|
|
263
|
+
this.updateQueue.set(orderKey, nextPromise);
|
|
264
|
+
return nextPromise;
|
|
265
|
+
}
|
|
266
|
+
async getSortedMessages(orderKey) {
|
|
267
|
+
const raw = await this.operations.getKV(TABLE_MESSAGES, orderKey);
|
|
268
|
+
if (!raw) return [];
|
|
269
|
+
try {
|
|
270
|
+
const arr = JSON.parse(typeof raw === "string" ? raw : JSON.stringify(raw));
|
|
271
|
+
return Array.isArray(arr) ? arr : [];
|
|
272
|
+
} catch (e) {
|
|
273
|
+
this.logger.error(`Error parsing order data for key ${orderKey}:`, { e });
|
|
274
|
+
return [];
|
|
256
275
|
}
|
|
257
276
|
}
|
|
258
|
-
async
|
|
259
|
-
tableName,
|
|
260
|
-
key,
|
|
261
|
-
value,
|
|
262
|
-
metadata
|
|
263
|
-
}) {
|
|
277
|
+
async migrateMessage(messageId, fromThreadId, toThreadId) {
|
|
264
278
|
try {
|
|
265
|
-
|
|
279
|
+
const oldMessageKey = this.getMessageKey(fromThreadId, messageId);
|
|
280
|
+
const message = await this.operations.getKV(TABLE_MESSAGES, oldMessageKey);
|
|
281
|
+
if (!message) return;
|
|
282
|
+
const updatedMessage = {
|
|
283
|
+
...message,
|
|
284
|
+
threadId: toThreadId
|
|
285
|
+
};
|
|
286
|
+
const newMessageKey = this.getMessageKey(toThreadId, messageId);
|
|
287
|
+
await this.operations.putKV({ tableName: TABLE_MESSAGES, key: newMessageKey, value: updatedMessage });
|
|
288
|
+
const oldOrderKey = this.getThreadMessagesKey(fromThreadId);
|
|
289
|
+
const oldEntries = await this.getSortedMessages(oldOrderKey);
|
|
290
|
+
const filteredEntries = oldEntries.filter((entry) => entry.id !== messageId);
|
|
291
|
+
await this.updateSortedMessages(oldOrderKey, filteredEntries);
|
|
292
|
+
const newOrderKey = this.getThreadMessagesKey(toThreadId);
|
|
293
|
+
const newEntries = await this.getSortedMessages(newOrderKey);
|
|
294
|
+
const newEntry = { id: messageId, score: Date.now() };
|
|
295
|
+
newEntries.push(newEntry);
|
|
296
|
+
await this.updateSortedMessages(newOrderKey, newEntries);
|
|
297
|
+
await this.operations.deleteKV(TABLE_MESSAGES, oldMessageKey);
|
|
266
298
|
} catch (error) {
|
|
267
|
-
this.logger
|
|
268
|
-
throw
|
|
299
|
+
this.logger?.error(`Error migrating message ${messageId} from ${fromThreadId} to ${toThreadId}:`, error);
|
|
300
|
+
throw error;
|
|
269
301
|
}
|
|
270
302
|
}
|
|
271
|
-
async
|
|
303
|
+
async saveMessages(args) {
|
|
304
|
+
const { messages } = args;
|
|
305
|
+
if (!Array.isArray(messages) || messages.length === 0) return { messages: [] };
|
|
272
306
|
try {
|
|
273
|
-
const
|
|
274
|
-
|
|
307
|
+
const validatedMessages = messages.map((message, index) => {
|
|
308
|
+
const errors = [];
|
|
309
|
+
if (!message.id) errors.push("id is required");
|
|
310
|
+
if (!message.threadId) errors.push("threadId is required");
|
|
311
|
+
if (!message.content) errors.push("content is required");
|
|
312
|
+
if (!message.role) errors.push("role is required");
|
|
313
|
+
if (!message.createdAt) errors.push("createdAt is required");
|
|
314
|
+
if (message.resourceId === null || message.resourceId === void 0) errors.push("resourceId is required");
|
|
315
|
+
if (errors.length > 0) {
|
|
316
|
+
throw new Error(`Invalid message at index ${index}: ${errors.join(", ")}`);
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...message,
|
|
320
|
+
createdAt: ensureDate(message.createdAt),
|
|
321
|
+
type: message.type || "v2",
|
|
322
|
+
_index: index
|
|
323
|
+
};
|
|
324
|
+
}).filter((m) => !!m);
|
|
325
|
+
const messageMigrationTasks = [];
|
|
326
|
+
for (const message of validatedMessages) {
|
|
327
|
+
const existingMessage = await this.findMessageInAnyThread(message.id);
|
|
328
|
+
console.info(`Checking message ${message.id}: existing=${existingMessage?.threadId}, new=${message.threadId}`);
|
|
329
|
+
if (existingMessage && existingMessage.threadId && existingMessage.threadId !== message.threadId) {
|
|
330
|
+
console.info(`Migrating message ${message.id} from ${existingMessage.threadId} to ${message.threadId}`);
|
|
331
|
+
messageMigrationTasks.push(this.migrateMessage(message.id, existingMessage.threadId, message.threadId));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
await Promise.all(messageMigrationTasks);
|
|
335
|
+
const messagesByThread = validatedMessages.reduce((acc, message) => {
|
|
336
|
+
if (message.threadId && !acc.has(message.threadId)) {
|
|
337
|
+
acc.set(message.threadId, []);
|
|
338
|
+
}
|
|
339
|
+
if (message.threadId) {
|
|
340
|
+
acc.get(message.threadId).push(message);
|
|
341
|
+
}
|
|
342
|
+
return acc;
|
|
343
|
+
}, /* @__PURE__ */ new Map());
|
|
344
|
+
await Promise.all(
|
|
345
|
+
Array.from(messagesByThread.entries()).map(async ([threadId, threadMessages]) => {
|
|
346
|
+
try {
|
|
347
|
+
const thread = await this.getThreadById({ threadId });
|
|
348
|
+
if (!thread) {
|
|
349
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
350
|
+
}
|
|
351
|
+
await Promise.all(
|
|
352
|
+
threadMessages.map(async (message) => {
|
|
353
|
+
const key = this.getMessageKey(threadId, message.id);
|
|
354
|
+
const { _index, ...cleanMessage } = message;
|
|
355
|
+
const serializedMessage = {
|
|
356
|
+
...cleanMessage,
|
|
357
|
+
createdAt: serializeDate(cleanMessage.createdAt)
|
|
358
|
+
};
|
|
359
|
+
console.info(`Saving message ${message.id} with content:`, {
|
|
360
|
+
content: serializedMessage.content,
|
|
361
|
+
contentType: typeof serializedMessage.content,
|
|
362
|
+
isArray: Array.isArray(serializedMessage.content)
|
|
363
|
+
});
|
|
364
|
+
await this.operations.putKV({ tableName: TABLE_MESSAGES, key, value: serializedMessage });
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
const orderKey = this.getThreadMessagesKey(threadId);
|
|
368
|
+
const entries = await this.updateSorting(threadMessages);
|
|
369
|
+
await this.updateSortedMessages(orderKey, entries);
|
|
370
|
+
const updatedThread = {
|
|
371
|
+
...thread,
|
|
372
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
373
|
+
};
|
|
374
|
+
await this.operations.putKV({
|
|
375
|
+
tableName: TABLE_THREADS,
|
|
376
|
+
key: this.operations.getKey(TABLE_THREADS, { id: threadId }),
|
|
377
|
+
value: updatedThread
|
|
378
|
+
});
|
|
379
|
+
} catch (error) {
|
|
380
|
+
throw new MastraError(
|
|
381
|
+
{
|
|
382
|
+
id: "CLOUDFLARE_STORAGE_SAVE_MESSAGES_FAILED",
|
|
383
|
+
domain: ErrorDomain.STORAGE,
|
|
384
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
385
|
+
details: {
|
|
386
|
+
threadId
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
error
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
const prepared = validatedMessages.map(
|
|
395
|
+
({ _index, ...message }) => ({ ...message, type: message.type !== "v2" ? message.type : void 0 })
|
|
396
|
+
);
|
|
397
|
+
const list = new MessageList().add(prepared, "memory");
|
|
398
|
+
return { messages: list.get.all.db() };
|
|
275
399
|
} catch (error) {
|
|
276
|
-
|
|
277
|
-
|
|
400
|
+
throw new MastraError(
|
|
401
|
+
{
|
|
402
|
+
id: "CLOUDFLARE_STORAGE_SAVE_MESSAGES_FAILED",
|
|
403
|
+
domain: ErrorDomain.STORAGE,
|
|
404
|
+
category: ErrorCategory.THIRD_PARTY
|
|
405
|
+
},
|
|
406
|
+
error
|
|
407
|
+
);
|
|
278
408
|
}
|
|
279
409
|
}
|
|
280
|
-
async
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.logger.error(`Failed to delete KV value for ${tableName}:${key}:`, error);
|
|
285
|
-
throw new Error(`Failed to delete KV value: ${error.message}`);
|
|
286
|
-
}
|
|
410
|
+
async getRank(orderKey, id) {
|
|
411
|
+
const order = await this.getSortedMessages(orderKey);
|
|
412
|
+
const index = order.findIndex((item) => item.id === id);
|
|
413
|
+
return index >= 0 ? index : null;
|
|
287
414
|
}
|
|
288
|
-
async
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
415
|
+
async getRange(orderKey, start, end) {
|
|
416
|
+
const order = await this.getSortedMessages(orderKey);
|
|
417
|
+
const actualStart = start < 0 ? Math.max(0, order.length + start) : start;
|
|
418
|
+
const actualEnd = end < 0 ? order.length + end : Math.min(end, order.length - 1);
|
|
419
|
+
const sliced = order.slice(actualStart, actualEnd + 1);
|
|
420
|
+
return sliced.map((item) => item.id);
|
|
295
421
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
We store an array of objects { id, score } as JSON under a dedicated key.
|
|
299
|
-
---------------------------------------------------------------------------*/
|
|
300
|
-
async getSortedMessages(orderKey) {
|
|
301
|
-
const raw = await this.getKV(TABLE_MESSAGES, orderKey);
|
|
302
|
-
if (!raw) return [];
|
|
303
|
-
try {
|
|
304
|
-
const arr = JSON.parse(typeof raw === "string" ? raw : JSON.stringify(raw));
|
|
305
|
-
return Array.isArray(arr) ? arr : [];
|
|
306
|
-
} catch (e) {
|
|
307
|
-
this.logger.error(`Error parsing order data for key ${orderKey}:`, { e });
|
|
308
|
-
return [];
|
|
309
|
-
}
|
|
422
|
+
async getLastN(orderKey, n) {
|
|
423
|
+
return this.getRange(orderKey, -n, -1);
|
|
310
424
|
}
|
|
311
|
-
async
|
|
312
|
-
return
|
|
313
|
-
message: msg,
|
|
314
|
-
// Use _index if available, otherwise timestamp, matching Upstash
|
|
315
|
-
score: msg._index !== void 0 ? msg._index : msg.createdAt.getTime()
|
|
316
|
-
})).sort((a, b) => a.score - b.score).map((item) => ({
|
|
317
|
-
id: item.message.id,
|
|
318
|
-
score: item.score
|
|
319
|
-
}));
|
|
425
|
+
async getFullOrder(orderKey) {
|
|
426
|
+
return this.getRange(orderKey, 0, -1);
|
|
320
427
|
}
|
|
321
428
|
async getIncludedMessagesWithContext(threadId, include, messageIds) {
|
|
322
|
-
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
323
429
|
await Promise.all(
|
|
324
430
|
include.map(async (item) => {
|
|
431
|
+
const targetThreadId = item.threadId || threadId;
|
|
432
|
+
if (!targetThreadId) return;
|
|
433
|
+
const threadMessagesKey = this.getThreadMessagesKey(targetThreadId);
|
|
325
434
|
messageIds.add(item.id);
|
|
326
435
|
if (!item.withPreviousMessages && !item.withNextMessages) return;
|
|
327
436
|
const rank = await this.getRank(threadMessagesKey, item.id);
|
|
@@ -342,23 +451,50 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
342
451
|
);
|
|
343
452
|
}
|
|
344
453
|
async getRecentMessages(threadId, limit, messageIds) {
|
|
454
|
+
if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
|
|
345
455
|
if (limit <= 0) return;
|
|
346
456
|
try {
|
|
347
457
|
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
348
458
|
const latestIds = await this.getLastN(threadMessagesKey, limit);
|
|
349
459
|
latestIds.forEach((id) => messageIds.add(id));
|
|
350
460
|
} catch {
|
|
351
|
-
console.
|
|
461
|
+
console.info(`No message order found for thread ${threadId}, skipping latest messages`);
|
|
352
462
|
}
|
|
353
463
|
}
|
|
354
|
-
async
|
|
464
|
+
async fetchAndParseMessagesFromMultipleThreads(messageIds, include, targetThreadId) {
|
|
465
|
+
const messageIdToThreadId = /* @__PURE__ */ new Map();
|
|
466
|
+
if (include) {
|
|
467
|
+
for (const item of include) {
|
|
468
|
+
if (item.threadId) {
|
|
469
|
+
messageIdToThreadId.set(item.id, item.threadId);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
355
473
|
const messages = await Promise.all(
|
|
356
474
|
messageIds.map(async (id) => {
|
|
357
475
|
try {
|
|
476
|
+
let threadId = messageIdToThreadId.get(id);
|
|
477
|
+
if (!threadId) {
|
|
478
|
+
if (targetThreadId) {
|
|
479
|
+
threadId = targetThreadId;
|
|
480
|
+
} else {
|
|
481
|
+
const foundMessage = await this.findMessageInAnyThread(id);
|
|
482
|
+
if (foundMessage) {
|
|
483
|
+
threadId = foundMessage.threadId;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!threadId) return null;
|
|
358
488
|
const key = this.getMessageKey(threadId, id);
|
|
359
|
-
const data = await this.getKV(TABLE_MESSAGES, key);
|
|
489
|
+
const data = await this.operations.getKV(TABLE_MESSAGES, key);
|
|
360
490
|
if (!data) return null;
|
|
361
|
-
|
|
491
|
+
const parsed = typeof data === "string" ? JSON.parse(data) : data;
|
|
492
|
+
console.info(`Retrieved message ${id} from thread ${threadId} with content:`, {
|
|
493
|
+
content: parsed.content,
|
|
494
|
+
contentType: typeof parsed.content,
|
|
495
|
+
isArray: Array.isArray(parsed.content)
|
|
496
|
+
});
|
|
497
|
+
return parsed;
|
|
362
498
|
} catch (error) {
|
|
363
499
|
const message = error instanceof Error ? error.message : String(error);
|
|
364
500
|
this.logger.error(`Error retrieving message ${id}:`, { message });
|
|
@@ -368,61 +504,581 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
368
504
|
);
|
|
369
505
|
return messages.filter((msg) => msg !== null);
|
|
370
506
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
507
|
+
async getMessages({
|
|
508
|
+
threadId,
|
|
509
|
+
resourceId,
|
|
510
|
+
selectBy
|
|
511
|
+
}) {
|
|
512
|
+
console.info(`getMessages called with threadId: ${threadId}`);
|
|
513
|
+
const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
|
|
514
|
+
const messageIds = /* @__PURE__ */ new Set();
|
|
515
|
+
if (limit === 0 && !selectBy?.include?.length) return { messages: [] };
|
|
516
|
+
try {
|
|
517
|
+
if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
|
|
518
|
+
await Promise.all([
|
|
519
|
+
selectBy?.include?.length ? this.getIncludedMessagesWithContext(threadId, selectBy.include, messageIds) : Promise.resolve(),
|
|
520
|
+
limit > 0 ? this.getRecentMessages(threadId, limit, messageIds) : Promise.resolve()
|
|
521
|
+
]);
|
|
522
|
+
const targetThreadId = selectBy?.include?.length ? void 0 : threadId;
|
|
523
|
+
const messages = await this.fetchAndParseMessagesFromMultipleThreads(
|
|
524
|
+
Array.from(messageIds),
|
|
525
|
+
selectBy?.include,
|
|
526
|
+
targetThreadId
|
|
527
|
+
);
|
|
528
|
+
if (!messages.length) return { messages: [] };
|
|
384
529
|
try {
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
key: orderKey,
|
|
394
|
-
value: JSON.stringify(updatedOrder)
|
|
530
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
531
|
+
const messageOrder = await this.getFullOrder(threadMessagesKey);
|
|
532
|
+
const orderMap = new Map(messageOrder.map((id, index) => [id, index]));
|
|
533
|
+
messages.sort((a, b) => {
|
|
534
|
+
const indexA = orderMap.get(a.id);
|
|
535
|
+
const indexB = orderMap.get(b.id);
|
|
536
|
+
if (indexA !== void 0 && indexB !== void 0) return orderMap.get(a.id) - orderMap.get(b.id);
|
|
537
|
+
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
395
538
|
});
|
|
396
539
|
} catch (error) {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
540
|
+
const mastraError = new MastraError(
|
|
541
|
+
{
|
|
542
|
+
id: "CLOUDFLARE_STORAGE_SORT_MESSAGES_FAILED",
|
|
543
|
+
domain: ErrorDomain.STORAGE,
|
|
544
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
545
|
+
text: `Error sorting messages for thread ${threadId} falling back to creation time`,
|
|
546
|
+
details: {
|
|
547
|
+
threadId
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
error
|
|
551
|
+
);
|
|
552
|
+
this.logger?.trackException(mastraError);
|
|
553
|
+
this.logger?.error(mastraError.toString());
|
|
554
|
+
messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
555
|
+
}
|
|
556
|
+
const prepared = messages.map(({ _index, ...message }) => ({
|
|
557
|
+
...message,
|
|
558
|
+
type: message.type === `v2` ? void 0 : message.type,
|
|
559
|
+
createdAt: ensureDate(message.createdAt)
|
|
560
|
+
}));
|
|
561
|
+
const list = new MessageList({ threadId, resourceId }).add(
|
|
562
|
+
prepared,
|
|
563
|
+
"memory"
|
|
564
|
+
);
|
|
565
|
+
return { messages: list.get.all.db() };
|
|
566
|
+
} catch (error) {
|
|
567
|
+
const mastraError = new MastraError(
|
|
568
|
+
{
|
|
569
|
+
id: "CLOUDFLARE_STORAGE_GET_MESSAGES_FAILED",
|
|
570
|
+
domain: ErrorDomain.STORAGE,
|
|
571
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
572
|
+
text: `Error retrieving messages for thread ${threadId}`,
|
|
573
|
+
details: {
|
|
574
|
+
threadId,
|
|
575
|
+
resourceId: resourceId ?? ""
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
error
|
|
579
|
+
);
|
|
580
|
+
this.logger?.trackException(mastraError);
|
|
581
|
+
this.logger?.error(mastraError.toString());
|
|
582
|
+
return { messages: [] };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async listMessagesById({ messageIds }) {
|
|
586
|
+
if (messageIds.length === 0) return { messages: [] };
|
|
587
|
+
try {
|
|
588
|
+
const messages = (await Promise.all(messageIds.map((id) => this.findMessageInAnyThread(id)))).filter(
|
|
589
|
+
(result) => !!result
|
|
590
|
+
);
|
|
591
|
+
const prepared = messages.map(({ _index, ...message }) => ({
|
|
592
|
+
...message,
|
|
593
|
+
...message.type !== `v2` && { type: message.type },
|
|
594
|
+
createdAt: ensureDate(message.createdAt)
|
|
595
|
+
}));
|
|
596
|
+
const list = new MessageList().add(prepared, "memory");
|
|
597
|
+
return { messages: list.get.all.db() };
|
|
598
|
+
} catch (error) {
|
|
599
|
+
const mastraError = new MastraError(
|
|
600
|
+
{
|
|
601
|
+
id: "CLOUDFLARE_STORAGE_LIST_MESSAGES_BY_ID_FAILED",
|
|
602
|
+
domain: ErrorDomain.STORAGE,
|
|
603
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
604
|
+
text: `Error retrieving messages by ID`,
|
|
605
|
+
details: {
|
|
606
|
+
messageIds: JSON.stringify(messageIds)
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
error
|
|
610
|
+
);
|
|
611
|
+
this.logger?.trackException(mastraError);
|
|
612
|
+
this.logger?.error(mastraError.toString());
|
|
613
|
+
return { messages: [] };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async listMessages(args) {
|
|
617
|
+
const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
|
|
618
|
+
if (!threadId.trim()) {
|
|
619
|
+
throw new MastraError(
|
|
620
|
+
{
|
|
621
|
+
id: "STORAGE_CLOUDFLARE_LIST_MESSAGES_INVALID_THREAD_ID",
|
|
622
|
+
domain: ErrorDomain.STORAGE,
|
|
623
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
624
|
+
details: { threadId }
|
|
625
|
+
},
|
|
626
|
+
new Error("threadId must be a non-empty string")
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const perPage = normalizePerPage(perPageInput, 40);
|
|
630
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
631
|
+
try {
|
|
632
|
+
if (page < 0) {
|
|
633
|
+
throw new MastraError(
|
|
634
|
+
{
|
|
635
|
+
id: "STORAGE_CLOUDFLARE_LIST_MESSAGES_INVALID_PAGE",
|
|
636
|
+
domain: ErrorDomain.STORAGE,
|
|
637
|
+
category: ErrorCategory.USER,
|
|
638
|
+
details: { page }
|
|
639
|
+
},
|
|
640
|
+
new Error("page must be >= 0")
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
644
|
+
const messageIds = /* @__PURE__ */ new Set();
|
|
645
|
+
const hasFilters = !!resourceId || !!filter?.dateRange;
|
|
646
|
+
if (hasFilters || perPage === Number.MAX_SAFE_INTEGER) {
|
|
647
|
+
try {
|
|
648
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
649
|
+
const allIds = await this.getFullOrder(threadMessagesKey);
|
|
650
|
+
allIds.forEach((id) => messageIds.add(id));
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
if (perPage > 0) {
|
|
655
|
+
try {
|
|
656
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
657
|
+
const fullOrder = await this.getFullOrder(threadMessagesKey);
|
|
658
|
+
const totalMessages = fullOrder.length;
|
|
659
|
+
let start;
|
|
660
|
+
let end;
|
|
661
|
+
if (direction === "ASC") {
|
|
662
|
+
start = offset;
|
|
663
|
+
end = Math.min(offset + perPage - 1, totalMessages - 1);
|
|
664
|
+
} else {
|
|
665
|
+
start = Math.max(totalMessages - offset - perPage, 0);
|
|
666
|
+
end = totalMessages - offset - 1;
|
|
667
|
+
}
|
|
668
|
+
const paginatedIds = await this.getRange(threadMessagesKey, start, end);
|
|
669
|
+
paginatedIds.forEach((id) => messageIds.add(id));
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
403
672
|
}
|
|
404
673
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
674
|
+
if (include && include.length > 0) {
|
|
675
|
+
await this.getIncludedMessagesWithContext(threadId, include, messageIds);
|
|
676
|
+
}
|
|
677
|
+
const messages = await this.fetchAndParseMessagesFromMultipleThreads(
|
|
678
|
+
Array.from(messageIds),
|
|
679
|
+
include,
|
|
680
|
+
include && include.length > 0 ? void 0 : threadId
|
|
681
|
+
);
|
|
682
|
+
let filteredMessages = messages;
|
|
683
|
+
if (resourceId) {
|
|
684
|
+
filteredMessages = filteredMessages.filter((msg) => msg.resourceId === resourceId);
|
|
685
|
+
}
|
|
686
|
+
const dateRange = filter?.dateRange;
|
|
687
|
+
if (dateRange) {
|
|
688
|
+
filteredMessages = filteredMessages.filter((msg) => {
|
|
689
|
+
const messageDate = new Date(msg.createdAt);
|
|
690
|
+
if (dateRange.start && messageDate < new Date(dateRange.start)) return false;
|
|
691
|
+
if (dateRange.end && messageDate > new Date(dateRange.end)) return false;
|
|
692
|
+
return true;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
let total;
|
|
696
|
+
if (hasFilters) {
|
|
697
|
+
total = filteredMessages.length;
|
|
698
|
+
} else {
|
|
699
|
+
try {
|
|
700
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
701
|
+
const fullOrder = await this.getFullOrder(threadMessagesKey);
|
|
702
|
+
total = fullOrder.length;
|
|
703
|
+
} catch {
|
|
704
|
+
total = filteredMessages.length;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (perPage === 0) {
|
|
708
|
+
return {
|
|
709
|
+
messages: [],
|
|
710
|
+
total,
|
|
711
|
+
page,
|
|
712
|
+
perPage: perPageForResponse,
|
|
713
|
+
hasMore: offset < total
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
if (hasFilters && perPage !== Number.MAX_SAFE_INTEGER && perPage > 0) {
|
|
717
|
+
if (direction === "ASC") {
|
|
718
|
+
filteredMessages = filteredMessages.slice(offset, offset + perPage);
|
|
719
|
+
} else {
|
|
720
|
+
const start = Math.max(filteredMessages.length - offset - perPage, 0);
|
|
721
|
+
const end = filteredMessages.length - offset;
|
|
722
|
+
filteredMessages = filteredMessages.slice(start, end);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const paginatedCount = hasFilters && perPage !== Number.MAX_SAFE_INTEGER && perPage > 0 ? filteredMessages.length : filteredMessages.length;
|
|
726
|
+
try {
|
|
727
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
728
|
+
const messageOrder = await this.getFullOrder(threadMessagesKey);
|
|
729
|
+
const orderMap = new Map(messageOrder.map((id, index) => [id, index]));
|
|
730
|
+
filteredMessages.sort((a, b) => {
|
|
731
|
+
const indexA = orderMap.get(a.id);
|
|
732
|
+
const indexB = orderMap.get(b.id);
|
|
733
|
+
if (indexA !== void 0 && indexB !== void 0) {
|
|
734
|
+
return direction === "ASC" ? indexA - indexB : indexB - indexA;
|
|
735
|
+
}
|
|
736
|
+
const timeA = new Date(a.createdAt).getTime();
|
|
737
|
+
const timeB = new Date(b.createdAt).getTime();
|
|
738
|
+
const timeDiff = direction === "ASC" ? timeA - timeB : timeB - timeA;
|
|
739
|
+
if (timeDiff === 0) {
|
|
740
|
+
return a.id.localeCompare(b.id);
|
|
741
|
+
}
|
|
742
|
+
return timeDiff;
|
|
743
|
+
});
|
|
744
|
+
} catch {
|
|
745
|
+
filteredMessages.sort((a, b) => {
|
|
746
|
+
const timeA = new Date(a.createdAt).getTime();
|
|
747
|
+
const timeB = new Date(b.createdAt).getTime();
|
|
748
|
+
const timeDiff = direction === "ASC" ? timeA - timeB : timeB - timeA;
|
|
749
|
+
if (timeDiff === 0) {
|
|
750
|
+
return a.id.localeCompare(b.id);
|
|
751
|
+
}
|
|
752
|
+
return timeDiff;
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (total === 0 && filteredMessages.length === 0 && (!include || include.length === 0)) {
|
|
756
|
+
return {
|
|
757
|
+
messages: [],
|
|
758
|
+
total: 0,
|
|
759
|
+
page,
|
|
760
|
+
perPage: perPageForResponse,
|
|
761
|
+
hasMore: false
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const prepared = filteredMessages.map(({ _index, ...message }) => ({
|
|
765
|
+
...message,
|
|
766
|
+
type: message.type === `v2` ? void 0 : message.type,
|
|
767
|
+
createdAt: ensureDate(message.createdAt)
|
|
768
|
+
}));
|
|
769
|
+
const list = new MessageList({ threadId, resourceId }).add(prepared, "memory");
|
|
770
|
+
let finalMessages = list.get.all.db();
|
|
771
|
+
finalMessages = finalMessages.sort((a, b) => {
|
|
772
|
+
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
773
|
+
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
774
|
+
if (aValue === bValue) {
|
|
775
|
+
return a.id.localeCompare(b.id);
|
|
776
|
+
}
|
|
777
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
778
|
+
});
|
|
779
|
+
const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
|
|
780
|
+
const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
|
|
781
|
+
let hasMore;
|
|
782
|
+
if (perPageInput === false || allThreadMessagesReturned) {
|
|
783
|
+
hasMore = false;
|
|
784
|
+
} else if (direction === "ASC") {
|
|
785
|
+
hasMore = offset + paginatedCount < total;
|
|
786
|
+
} else {
|
|
787
|
+
hasMore = total - offset - perPage > 0;
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
messages: finalMessages,
|
|
791
|
+
total,
|
|
792
|
+
page,
|
|
793
|
+
perPage: perPageForResponse,
|
|
794
|
+
hasMore
|
|
795
|
+
};
|
|
796
|
+
} catch (error) {
|
|
797
|
+
const mastraError = new MastraError(
|
|
798
|
+
{
|
|
799
|
+
id: "CLOUDFLARE_STORAGE_LIST_MESSAGES_FAILED",
|
|
800
|
+
domain: ErrorDomain.STORAGE,
|
|
801
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
802
|
+
text: `Failed to list messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
803
|
+
details: {
|
|
804
|
+
threadId,
|
|
805
|
+
resourceId: resourceId ?? ""
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
error
|
|
809
|
+
);
|
|
810
|
+
this.logger?.error?.(mastraError.toString());
|
|
811
|
+
this.logger?.trackException?.(mastraError);
|
|
812
|
+
return {
|
|
813
|
+
messages: [],
|
|
814
|
+
total: 0,
|
|
815
|
+
page,
|
|
816
|
+
perPage: perPageForResponse,
|
|
817
|
+
hasMore: false
|
|
818
|
+
};
|
|
819
|
+
}
|
|
408
820
|
}
|
|
409
|
-
async
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
821
|
+
async updateMessages(args) {
|
|
822
|
+
try {
|
|
823
|
+
const { messages } = args;
|
|
824
|
+
const updatedMessages = [];
|
|
825
|
+
for (const messageUpdate of messages) {
|
|
826
|
+
const { id, content, ...otherFields } = messageUpdate;
|
|
827
|
+
const prefix = this.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
|
|
828
|
+
const keyObjs = await this.operations.listKV(TABLE_MESSAGES, { prefix: `${prefix}${TABLE_MESSAGES}` });
|
|
829
|
+
let existingMessage = null;
|
|
830
|
+
let messageKey = "";
|
|
831
|
+
for (const { name: key } of keyObjs) {
|
|
832
|
+
const data = await this.operations.getKV(TABLE_MESSAGES, key);
|
|
833
|
+
if (data && data.id === id) {
|
|
834
|
+
existingMessage = data;
|
|
835
|
+
messageKey = key;
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (!existingMessage) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const updatedMessage = {
|
|
843
|
+
...existingMessage,
|
|
844
|
+
...otherFields,
|
|
845
|
+
id
|
|
846
|
+
};
|
|
847
|
+
if (content) {
|
|
848
|
+
if (content.metadata !== void 0) {
|
|
849
|
+
updatedMessage.content = {
|
|
850
|
+
...updatedMessage.content,
|
|
851
|
+
metadata: {
|
|
852
|
+
...updatedMessage.content?.metadata,
|
|
853
|
+
...content.metadata
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (content.content !== void 0) {
|
|
858
|
+
updatedMessage.content = {
|
|
859
|
+
...updatedMessage.content,
|
|
860
|
+
content: content.content
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if ("threadId" in messageUpdate && messageUpdate.threadId && messageUpdate.threadId !== existingMessage.threadId) {
|
|
865
|
+
await this.operations.deleteKV(TABLE_MESSAGES, messageKey);
|
|
866
|
+
updatedMessage.threadId = messageUpdate.threadId;
|
|
867
|
+
const newMessageKey = this.getMessageKey(messageUpdate.threadId, id);
|
|
868
|
+
await this.operations.putKV({
|
|
869
|
+
tableName: TABLE_MESSAGES,
|
|
870
|
+
key: newMessageKey,
|
|
871
|
+
value: updatedMessage
|
|
872
|
+
});
|
|
873
|
+
if (existingMessage.threadId) {
|
|
874
|
+
const sourceOrderKey = this.getThreadMessagesKey(existingMessage.threadId);
|
|
875
|
+
const sourceEntries = await this.getSortedMessages(sourceOrderKey);
|
|
876
|
+
const filteredEntries = sourceEntries.filter((entry) => entry.id !== id);
|
|
877
|
+
await this.updateSortedMessages(sourceOrderKey, filteredEntries);
|
|
878
|
+
}
|
|
879
|
+
const destOrderKey = this.getThreadMessagesKey(messageUpdate.threadId);
|
|
880
|
+
const destEntries = await this.getSortedMessages(destOrderKey);
|
|
881
|
+
const newEntry = { id, score: Date.now() };
|
|
882
|
+
destEntries.push(newEntry);
|
|
883
|
+
await this.updateSortedMessages(destOrderKey, destEntries);
|
|
884
|
+
} else {
|
|
885
|
+
await this.operations.putKV({
|
|
886
|
+
tableName: TABLE_MESSAGES,
|
|
887
|
+
key: messageKey,
|
|
888
|
+
value: updatedMessage
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
const threadsToUpdate = /* @__PURE__ */ new Set();
|
|
892
|
+
if (updatedMessage.threadId) {
|
|
893
|
+
threadsToUpdate.add(updatedMessage.threadId);
|
|
894
|
+
}
|
|
895
|
+
if ("threadId" in messageUpdate && messageUpdate.threadId && messageUpdate.threadId !== existingMessage.threadId) {
|
|
896
|
+
if (existingMessage.threadId) {
|
|
897
|
+
threadsToUpdate.add(existingMessage.threadId);
|
|
898
|
+
}
|
|
899
|
+
threadsToUpdate.add(messageUpdate.threadId);
|
|
900
|
+
}
|
|
901
|
+
for (const threadId of threadsToUpdate) {
|
|
902
|
+
const thread = await this.getThreadById({ threadId });
|
|
903
|
+
if (thread) {
|
|
904
|
+
const updatedThread = {
|
|
905
|
+
...thread,
|
|
906
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
907
|
+
};
|
|
908
|
+
await this.operations.putKV({
|
|
909
|
+
tableName: TABLE_THREADS,
|
|
910
|
+
key: this.operations.getKey(TABLE_THREADS, { id: threadId }),
|
|
911
|
+
value: updatedThread
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
updatedMessages.push(updatedMessage);
|
|
916
|
+
}
|
|
917
|
+
return updatedMessages;
|
|
918
|
+
} catch (error) {
|
|
919
|
+
throw new MastraError(
|
|
920
|
+
{
|
|
921
|
+
id: "CLOUDFLARE_STORAGE_UPDATE_MESSAGES_FAILED",
|
|
922
|
+
domain: ErrorDomain.STORAGE,
|
|
923
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
924
|
+
text: "Failed to update messages"
|
|
925
|
+
},
|
|
926
|
+
error
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async getResourceById({ resourceId }) {
|
|
931
|
+
try {
|
|
932
|
+
const data = await this.operations.getKV(TABLE_RESOURCES, resourceId);
|
|
933
|
+
if (!data) return null;
|
|
934
|
+
const resource = typeof data === "string" ? JSON.parse(data) : data;
|
|
935
|
+
return {
|
|
936
|
+
...resource,
|
|
937
|
+
createdAt: ensureDate(resource.createdAt),
|
|
938
|
+
updatedAt: ensureDate(resource.updatedAt),
|
|
939
|
+
metadata: this.ensureMetadata(resource.metadata)
|
|
940
|
+
};
|
|
941
|
+
} catch (error) {
|
|
942
|
+
const mastraError = new MastraError(
|
|
943
|
+
{
|
|
944
|
+
id: "CLOUDFLARE_STORAGE_GET_RESOURCE_BY_ID_FAILED",
|
|
945
|
+
domain: ErrorDomain.STORAGE,
|
|
946
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
947
|
+
details: {
|
|
948
|
+
resourceId
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
error
|
|
952
|
+
);
|
|
953
|
+
this.logger?.trackException(mastraError);
|
|
954
|
+
this.logger?.error(mastraError.toString());
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
async saveResource({ resource }) {
|
|
959
|
+
try {
|
|
960
|
+
const resourceToSave = {
|
|
961
|
+
...resource,
|
|
962
|
+
metadata: resource.metadata ? JSON.stringify(resource.metadata) : null
|
|
963
|
+
};
|
|
964
|
+
await this.operations.putKV({
|
|
965
|
+
tableName: TABLE_RESOURCES,
|
|
966
|
+
key: resource.id,
|
|
967
|
+
value: resourceToSave
|
|
968
|
+
});
|
|
969
|
+
return resource;
|
|
970
|
+
} catch (error) {
|
|
971
|
+
throw new MastraError(
|
|
972
|
+
{
|
|
973
|
+
id: "CLOUDFLARE_STORAGE_SAVE_RESOURCE_FAILED",
|
|
974
|
+
domain: ErrorDomain.STORAGE,
|
|
975
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
976
|
+
details: {
|
|
977
|
+
resourceId: resource.id
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
error
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async updateResource({
|
|
985
|
+
resourceId,
|
|
986
|
+
workingMemory,
|
|
987
|
+
metadata
|
|
988
|
+
}) {
|
|
989
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
990
|
+
if (!existingResource) {
|
|
991
|
+
const newResource = {
|
|
992
|
+
id: resourceId,
|
|
993
|
+
workingMemory,
|
|
994
|
+
metadata: metadata || {},
|
|
995
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
996
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
997
|
+
};
|
|
998
|
+
return this.saveResource({ resource: newResource });
|
|
999
|
+
}
|
|
1000
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
1001
|
+
const updatedResource = {
|
|
1002
|
+
...existingResource,
|
|
1003
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
1004
|
+
metadata: {
|
|
1005
|
+
...existingResource.metadata,
|
|
1006
|
+
...metadata
|
|
1007
|
+
},
|
|
1008
|
+
updatedAt
|
|
1009
|
+
};
|
|
1010
|
+
return this.saveResource({ resource: updatedResource });
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
var StoreOperationsCloudflare = class extends StoreOperations {
|
|
1014
|
+
bindings;
|
|
1015
|
+
client;
|
|
1016
|
+
accountId;
|
|
1017
|
+
namespacePrefix;
|
|
1018
|
+
constructor({
|
|
1019
|
+
namespacePrefix,
|
|
1020
|
+
bindings,
|
|
1021
|
+
client,
|
|
1022
|
+
accountId
|
|
1023
|
+
}) {
|
|
1024
|
+
super();
|
|
1025
|
+
this.bindings = bindings;
|
|
1026
|
+
this.namespacePrefix = namespacePrefix;
|
|
1027
|
+
this.client = client;
|
|
1028
|
+
this.accountId = accountId;
|
|
413
1029
|
}
|
|
414
|
-
async
|
|
415
|
-
|
|
416
|
-
const actualStart = start < 0 ? Math.max(0, order.length + start) : start;
|
|
417
|
-
const actualEnd = end < 0 ? order.length + end : Math.min(end, order.length - 1);
|
|
418
|
-
const sliced = order.slice(actualStart, actualEnd + 1);
|
|
419
|
-
return sliced.map((item) => item.id);
|
|
1030
|
+
async hasColumn() {
|
|
1031
|
+
return true;
|
|
420
1032
|
}
|
|
421
|
-
async
|
|
422
|
-
return this.getRange(orderKey, -n, -1);
|
|
1033
|
+
async alterTable(_args) {
|
|
423
1034
|
}
|
|
424
|
-
async
|
|
425
|
-
|
|
1035
|
+
async clearTable({ tableName }) {
|
|
1036
|
+
try {
|
|
1037
|
+
const keys = await this.listKV(tableName);
|
|
1038
|
+
if (keys.length > 0) {
|
|
1039
|
+
await Promise.all(keys.map((keyObj) => this.deleteKV(tableName, keyObj.name)));
|
|
1040
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
throw new MastraError(
|
|
1043
|
+
{
|
|
1044
|
+
id: "CLOUDFLARE_STORAGE_CLEAR_TABLE_FAILED",
|
|
1045
|
+
domain: ErrorDomain.STORAGE,
|
|
1046
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1047
|
+
details: {
|
|
1048
|
+
tableName
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
error
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
async dropTable({ tableName }) {
|
|
1056
|
+
try {
|
|
1057
|
+
const keys = await this.listKV(tableName);
|
|
1058
|
+
if (keys.length > 0) {
|
|
1059
|
+
await Promise.all(keys.map((keyObj) => this.deleteKV(tableName, keyObj.name)));
|
|
1060
|
+
}
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
throw new MastraError(
|
|
1063
|
+
{
|
|
1064
|
+
id: "CLOUDFLARE_STORAGE_DROP_TABLE_FAILED",
|
|
1065
|
+
domain: ErrorDomain.STORAGE,
|
|
1066
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1067
|
+
details: {
|
|
1068
|
+
tableName
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
error
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
getBinding(tableName) {
|
|
1076
|
+
if (!this.bindings) {
|
|
1077
|
+
throw new Error(`Cannot use Workers API binding for ${tableName}: Store initialized with REST API configuration`);
|
|
1078
|
+
}
|
|
1079
|
+
const binding = this.bindings[tableName];
|
|
1080
|
+
if (!binding) throw new Error(`No binding found for namespace ${tableName}`);
|
|
1081
|
+
return binding;
|
|
426
1082
|
}
|
|
427
1083
|
getKey(tableName, record) {
|
|
428
1084
|
const prefix = this.namespacePrefix ? `${this.namespacePrefix}:` : "";
|
|
@@ -434,10 +1090,10 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
434
1090
|
if (!record.threadId || !record.id) throw new Error("Thread ID and Message ID are required");
|
|
435
1091
|
return `${prefix}${tableName}:${record.threadId}:${record.id}`;
|
|
436
1092
|
case TABLE_WORKFLOW_SNAPSHOT:
|
|
437
|
-
if (!record.
|
|
438
|
-
throw new Error("
|
|
1093
|
+
if (!record.workflow_name || !record.run_id) {
|
|
1094
|
+
throw new Error("Workflow name, and run ID are required");
|
|
439
1095
|
}
|
|
440
|
-
let key = `${prefix}${tableName}:${record.
|
|
1096
|
+
let key = `${prefix}${tableName}:${record.workflow_name}:${record.run_id}`;
|
|
441
1097
|
if (record.resourceId) {
|
|
442
1098
|
key = `${key}:${record.resourceId}`;
|
|
443
1099
|
}
|
|
@@ -445,6 +1101,9 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
445
1101
|
case TABLE_TRACES:
|
|
446
1102
|
if (!record.id) throw new Error("Trace ID is required");
|
|
447
1103
|
return `${prefix}${tableName}:${record.id}`;
|
|
1104
|
+
case TABLE_SCORERS:
|
|
1105
|
+
if (!record.id) throw new Error("Score ID is required");
|
|
1106
|
+
return `${prefix}${tableName}:${record.id}`;
|
|
448
1107
|
default:
|
|
449
1108
|
throw new Error(`Unsupported table: ${tableName}`);
|
|
450
1109
|
}
|
|
@@ -453,6 +1112,146 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
453
1112
|
const prefix = this.namespacePrefix ? `${this.namespacePrefix}:` : "";
|
|
454
1113
|
return `${prefix}schema:${tableName}`;
|
|
455
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Helper to safely parse data from KV storage
|
|
1117
|
+
*/
|
|
1118
|
+
safeParse(text) {
|
|
1119
|
+
if (!text) return null;
|
|
1120
|
+
try {
|
|
1121
|
+
const data = JSON.parse(text);
|
|
1122
|
+
if (data && typeof data === "object" && "value" in data) {
|
|
1123
|
+
if (typeof data.value === "string") {
|
|
1124
|
+
try {
|
|
1125
|
+
return JSON.parse(data.value);
|
|
1126
|
+
} catch {
|
|
1127
|
+
return data.value;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
return data;
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1135
|
+
this.logger.error("Failed to parse text:", { message, text });
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
async createNamespaceById(title) {
|
|
1140
|
+
if (this.bindings) {
|
|
1141
|
+
return {
|
|
1142
|
+
id: title,
|
|
1143
|
+
// Use title as ID since that's what we need
|
|
1144
|
+
title,
|
|
1145
|
+
supports_url_encoding: true
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
return await this.client.kv.namespaces.create({
|
|
1149
|
+
account_id: this.accountId,
|
|
1150
|
+
title
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
async createNamespace(namespaceName) {
|
|
1154
|
+
try {
|
|
1155
|
+
const response = await this.createNamespaceById(namespaceName);
|
|
1156
|
+
return response.id;
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (error.message && error.message.includes("already exists")) {
|
|
1159
|
+
const namespaces = await this.listNamespaces();
|
|
1160
|
+
const namespace = namespaces.result.find((ns) => ns.title === namespaceName);
|
|
1161
|
+
if (namespace) return namespace.id;
|
|
1162
|
+
}
|
|
1163
|
+
this.logger.error("Error creating namespace:", error);
|
|
1164
|
+
throw new Error(`Failed to create namespace ${namespaceName}: ${error.message}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
async listNamespaces() {
|
|
1168
|
+
if (this.bindings) {
|
|
1169
|
+
return {
|
|
1170
|
+
result: Object.keys(this.bindings).map((name) => ({
|
|
1171
|
+
id: name,
|
|
1172
|
+
title: name,
|
|
1173
|
+
supports_url_encoding: true
|
|
1174
|
+
}))
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
let allNamespaces = [];
|
|
1178
|
+
let currentPage = 1;
|
|
1179
|
+
const perPage = 50;
|
|
1180
|
+
let morePagesExist = true;
|
|
1181
|
+
while (morePagesExist) {
|
|
1182
|
+
const response = await this.client.kv.namespaces.list({
|
|
1183
|
+
account_id: this.accountId,
|
|
1184
|
+
page: currentPage,
|
|
1185
|
+
per_page: perPage
|
|
1186
|
+
});
|
|
1187
|
+
if (response.result) {
|
|
1188
|
+
allNamespaces = allNamespaces.concat(response.result);
|
|
1189
|
+
}
|
|
1190
|
+
morePagesExist = response.result ? response.result.length === perPage : false;
|
|
1191
|
+
if (morePagesExist) {
|
|
1192
|
+
currentPage++;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return { result: allNamespaces };
|
|
1196
|
+
}
|
|
1197
|
+
async getNamespaceIdByName(namespaceName) {
|
|
1198
|
+
try {
|
|
1199
|
+
const response = await this.listNamespaces();
|
|
1200
|
+
const namespace = response.result.find((ns) => ns.title === namespaceName);
|
|
1201
|
+
return namespace ? namespace.id : null;
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
this.logger.error(`Failed to get namespace ID for ${namespaceName}:`, error);
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async getOrCreateNamespaceId(namespaceName) {
|
|
1208
|
+
let namespaceId = await this.getNamespaceIdByName(namespaceName);
|
|
1209
|
+
if (!namespaceId) {
|
|
1210
|
+
namespaceId = await this.createNamespace(namespaceName);
|
|
1211
|
+
}
|
|
1212
|
+
return namespaceId;
|
|
1213
|
+
}
|
|
1214
|
+
async getNamespaceId(tableName) {
|
|
1215
|
+
const prefix = this.namespacePrefix ? `${this.namespacePrefix}_` : "";
|
|
1216
|
+
try {
|
|
1217
|
+
return await this.getOrCreateNamespaceId(`${prefix}${tableName}`);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
this.logger.error("Error fetching namespace ID:", error);
|
|
1220
|
+
throw new Error(`Failed to fetch namespace ID for table ${tableName}: ${error.message}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async getNamespaceValue(tableName, key) {
|
|
1224
|
+
try {
|
|
1225
|
+
if (this.bindings) {
|
|
1226
|
+
const binding = this.getBinding(tableName);
|
|
1227
|
+
const result = await binding.getWithMetadata(key, "text");
|
|
1228
|
+
if (!result) return null;
|
|
1229
|
+
return JSON.stringify(result);
|
|
1230
|
+
} else {
|
|
1231
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
1232
|
+
const response = await this.client.kv.namespaces.values.get(namespaceId, key, {
|
|
1233
|
+
account_id: this.accountId
|
|
1234
|
+
});
|
|
1235
|
+
return await response.text();
|
|
1236
|
+
}
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
if (error.message && error.message.includes("key not found")) {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1242
|
+
this.logger.error(`Failed to get value for ${tableName} ${key}:`, { message });
|
|
1243
|
+
throw error;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async getKV(tableName, key) {
|
|
1247
|
+
try {
|
|
1248
|
+
const text = await this.getNamespaceValue(tableName, key);
|
|
1249
|
+
return this.safeParse(text);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
this.logger.error(`Failed to get KV value for ${tableName}:${key}:`, error);
|
|
1252
|
+
throw new Error(`Failed to get KV value: ${error.message}`);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
456
1255
|
async getTableSchema(tableName) {
|
|
457
1256
|
try {
|
|
458
1257
|
const schemaKey = this.getSchemaKey(tableName);
|
|
@@ -505,64 +1304,168 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
505
1304
|
}
|
|
506
1305
|
} catch (error) {
|
|
507
1306
|
const message = error instanceof Error ? error.message : String(error);
|
|
508
|
-
this.logger.error(`Error validating record against schema:`, { message, record, schema });
|
|
1307
|
+
this.logger.error(`Error validating record against schema:`, { message, record, schema });
|
|
1308
|
+
throw error;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
async validateRecord(record, tableName) {
|
|
1312
|
+
try {
|
|
1313
|
+
if (!record || typeof record !== "object") {
|
|
1314
|
+
throw new Error("Record must be an object");
|
|
1315
|
+
}
|
|
1316
|
+
const recordTyped = record;
|
|
1317
|
+
const schema = await this.getTableSchema(tableName);
|
|
1318
|
+
if (schema) {
|
|
1319
|
+
await this.validateAgainstSchema(recordTyped, schema);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
switch (tableName) {
|
|
1323
|
+
case TABLE_THREADS:
|
|
1324
|
+
if (!("id" in recordTyped) || !("resourceId" in recordTyped) || !("title" in recordTyped)) {
|
|
1325
|
+
throw new Error("Thread record missing required fields");
|
|
1326
|
+
}
|
|
1327
|
+
break;
|
|
1328
|
+
case TABLE_MESSAGES:
|
|
1329
|
+
if (!("id" in recordTyped) || !("threadId" in recordTyped) || !("content" in recordTyped) || !("role" in recordTyped)) {
|
|
1330
|
+
throw new Error("Message record missing required fields");
|
|
1331
|
+
}
|
|
1332
|
+
break;
|
|
1333
|
+
case TABLE_WORKFLOW_SNAPSHOT:
|
|
1334
|
+
if (!("workflow_name" in recordTyped) || !("run_id" in recordTyped)) {
|
|
1335
|
+
throw new Error("Workflow record missing required fields");
|
|
1336
|
+
}
|
|
1337
|
+
break;
|
|
1338
|
+
case TABLE_TRACES:
|
|
1339
|
+
if (!("id" in recordTyped)) {
|
|
1340
|
+
throw new Error("Trace record missing required fields");
|
|
1341
|
+
}
|
|
1342
|
+
break;
|
|
1343
|
+
case TABLE_SCORERS:
|
|
1344
|
+
if (!("id" in recordTyped) || !("scorerId" in recordTyped)) {
|
|
1345
|
+
throw new Error("Score record missing required fields");
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
default:
|
|
1349
|
+
throw new Error(`Unknown table type: ${tableName}`);
|
|
1350
|
+
}
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1353
|
+
this.logger.error(`Failed to validate record for ${tableName}:`, { message, record });
|
|
1354
|
+
throw error;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
async insert({ tableName, record }) {
|
|
1358
|
+
try {
|
|
1359
|
+
const key = this.getKey(tableName, record);
|
|
1360
|
+
const processedRecord = { ...record };
|
|
1361
|
+
await this.validateRecord(processedRecord, tableName);
|
|
1362
|
+
await this.putKV({ tableName, key, value: processedRecord });
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
throw new MastraError(
|
|
1365
|
+
{
|
|
1366
|
+
id: "CLOUDFLARE_STORAGE_INSERT_FAILED",
|
|
1367
|
+
domain: ErrorDomain.STORAGE,
|
|
1368
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1369
|
+
details: {
|
|
1370
|
+
tableName
|
|
1371
|
+
}
|
|
1372
|
+
},
|
|
1373
|
+
error
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async load({ tableName, keys }) {
|
|
1378
|
+
try {
|
|
1379
|
+
const key = this.getKey(tableName, keys);
|
|
1380
|
+
const data = await this.getKV(tableName, key);
|
|
1381
|
+
if (!data) return null;
|
|
1382
|
+
return data;
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
const mastraError = new MastraError(
|
|
1385
|
+
{
|
|
1386
|
+
id: "CLOUDFLARE_STORAGE_LOAD_FAILED",
|
|
1387
|
+
domain: ErrorDomain.STORAGE,
|
|
1388
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1389
|
+
details: {
|
|
1390
|
+
tableName
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
error
|
|
1394
|
+
);
|
|
1395
|
+
this.logger?.trackException(mastraError);
|
|
1396
|
+
this.logger?.error(mastraError.toString());
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async batchInsert(input) {
|
|
1401
|
+
if (!input.records || input.records.length === 0) return;
|
|
1402
|
+
try {
|
|
1403
|
+
await Promise.all(
|
|
1404
|
+
input.records.map(async (record) => {
|
|
1405
|
+
const key = this.getKey(input.tableName, record);
|
|
1406
|
+
await this.putKV({ tableName: input.tableName, key, value: record });
|
|
1407
|
+
})
|
|
1408
|
+
);
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
throw new MastraError(
|
|
1411
|
+
{
|
|
1412
|
+
id: "CLOUDFLARE_STORAGE_BATCH_INSERT_FAILED",
|
|
1413
|
+
domain: ErrorDomain.STORAGE,
|
|
1414
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1415
|
+
text: `Error in batch insert for table ${input.tableName}`,
|
|
1416
|
+
details: {
|
|
1417
|
+
tableName: input.tableName
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
error
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Helper to safely serialize data for KV storage
|
|
1426
|
+
*/
|
|
1427
|
+
safeSerialize(data) {
|
|
1428
|
+
return typeof data === "string" ? data : JSON.stringify(data);
|
|
1429
|
+
}
|
|
1430
|
+
async putNamespaceValue({
|
|
1431
|
+
tableName,
|
|
1432
|
+
key,
|
|
1433
|
+
value,
|
|
1434
|
+
metadata
|
|
1435
|
+
}) {
|
|
1436
|
+
try {
|
|
1437
|
+
const serializedValue = this.safeSerialize(value);
|
|
1438
|
+
const serializedMetadata = metadata ? this.safeSerialize(metadata) : "";
|
|
1439
|
+
if (this.bindings) {
|
|
1440
|
+
const binding = this.getBinding(tableName);
|
|
1441
|
+
await binding.put(key, serializedValue, { metadata: serializedMetadata });
|
|
1442
|
+
} else {
|
|
1443
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
1444
|
+
await this.client.kv.namespaces.values.update(namespaceId, key, {
|
|
1445
|
+
account_id: this.accountId,
|
|
1446
|
+
value: serializedValue,
|
|
1447
|
+
metadata: serializedMetadata
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1452
|
+
this.logger.error(`Failed to put value for ${tableName} ${key}:`, { message });
|
|
509
1453
|
throw error;
|
|
510
1454
|
}
|
|
511
1455
|
}
|
|
512
|
-
async
|
|
1456
|
+
async putKV({
|
|
1457
|
+
tableName,
|
|
1458
|
+
key,
|
|
1459
|
+
value,
|
|
1460
|
+
metadata
|
|
1461
|
+
}) {
|
|
513
1462
|
try {
|
|
514
|
-
|
|
515
|
-
throw new Error("Record must be an object");
|
|
516
|
-
}
|
|
517
|
-
const recordTyped = record;
|
|
518
|
-
const schema = await this.getTableSchema(tableName);
|
|
519
|
-
if (schema) {
|
|
520
|
-
await this.validateAgainstSchema(recordTyped, schema);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
switch (tableName) {
|
|
524
|
-
case TABLE_THREADS:
|
|
525
|
-
if (!("id" in recordTyped) || !("resourceId" in recordTyped) || !("title" in recordTyped)) {
|
|
526
|
-
throw new Error("Thread record missing required fields");
|
|
527
|
-
}
|
|
528
|
-
break;
|
|
529
|
-
case TABLE_MESSAGES:
|
|
530
|
-
if (!("id" in recordTyped) || !("threadId" in recordTyped) || !("content" in recordTyped) || !("role" in recordTyped)) {
|
|
531
|
-
throw new Error("Message record missing required fields");
|
|
532
|
-
}
|
|
533
|
-
break;
|
|
534
|
-
case TABLE_WORKFLOW_SNAPSHOT:
|
|
535
|
-
if (!("namespace" in recordTyped) || !("workflow_name" in recordTyped) || !("run_id" in recordTyped)) {
|
|
536
|
-
throw new Error("Workflow record missing required fields");
|
|
537
|
-
}
|
|
538
|
-
break;
|
|
539
|
-
case TABLE_TRACES:
|
|
540
|
-
if (!("id" in recordTyped)) {
|
|
541
|
-
throw new Error("Trace record missing required fields");
|
|
542
|
-
}
|
|
543
|
-
break;
|
|
544
|
-
default:
|
|
545
|
-
throw new Error(`Unknown table type: ${tableName}`);
|
|
546
|
-
}
|
|
1463
|
+
await this.putNamespaceValue({ tableName, key, value, metadata });
|
|
547
1464
|
} catch (error) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
throw error;
|
|
1465
|
+
this.logger.error(`Failed to put KV value for ${tableName}:${key}:`, error);
|
|
1466
|
+
throw new Error(`Failed to put KV value: ${error.message}`);
|
|
551
1467
|
}
|
|
552
1468
|
}
|
|
553
|
-
ensureDate(date) {
|
|
554
|
-
if (!date) return void 0;
|
|
555
|
-
return date instanceof Date ? date : new Date(date);
|
|
556
|
-
}
|
|
557
|
-
serializeDate(date) {
|
|
558
|
-
if (!date) return void 0;
|
|
559
|
-
const dateObj = this.ensureDate(date);
|
|
560
|
-
return dateObj?.toISOString();
|
|
561
|
-
}
|
|
562
|
-
ensureMetadata(metadata) {
|
|
563
|
-
if (!metadata) return {};
|
|
564
|
-
return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
|
|
565
|
-
}
|
|
566
1469
|
async createTable({
|
|
567
1470
|
tableName,
|
|
568
1471
|
schema
|
|
@@ -576,475 +1479,467 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
576
1479
|
};
|
|
577
1480
|
await this.putKV({ tableName, key: schemaKey, value: schema, metadata });
|
|
578
1481
|
} catch (error) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1482
|
+
throw new MastraError(
|
|
1483
|
+
{
|
|
1484
|
+
id: "CLOUDFLARE_STORAGE_CREATE_TABLE_FAILED",
|
|
1485
|
+
domain: ErrorDomain.STORAGE,
|
|
1486
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1487
|
+
details: {
|
|
1488
|
+
tableName
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
error
|
|
1492
|
+
);
|
|
587
1493
|
}
|
|
588
1494
|
}
|
|
589
|
-
async
|
|
590
|
-
tableName,
|
|
591
|
-
record
|
|
592
|
-
}) {
|
|
1495
|
+
async listNamespaceKeys(tableName, options) {
|
|
593
1496
|
try {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1497
|
+
if (this.bindings) {
|
|
1498
|
+
const binding = this.getBinding(tableName);
|
|
1499
|
+
const response = await binding.list({
|
|
1500
|
+
limit: options?.limit || 1e3,
|
|
1501
|
+
prefix: options?.prefix
|
|
1502
|
+
});
|
|
1503
|
+
return response.keys;
|
|
1504
|
+
} else {
|
|
1505
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
1506
|
+
const response = await this.client.kv.namespaces.keys.list(namespaceId, {
|
|
1507
|
+
account_id: this.accountId,
|
|
1508
|
+
limit: options?.limit || 1e3,
|
|
1509
|
+
prefix: options?.prefix
|
|
1510
|
+
});
|
|
1511
|
+
return response.result;
|
|
1512
|
+
}
|
|
603
1513
|
} catch (error) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
1514
|
+
throw new MastraError(
|
|
1515
|
+
{
|
|
1516
|
+
id: "CLOUDFLARE_STORAGE_LIST_NAMESPACE_KEYS_FAILED",
|
|
1517
|
+
domain: ErrorDomain.STORAGE,
|
|
1518
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1519
|
+
details: {
|
|
1520
|
+
tableName
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
error
|
|
1524
|
+
);
|
|
607
1525
|
}
|
|
608
1526
|
}
|
|
609
|
-
async
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
updatedAt: this.ensureDate(data.updatedAt),
|
|
618
|
-
metadata: this.ensureMetadata(data.metadata)
|
|
619
|
-
};
|
|
620
|
-
return processed;
|
|
621
|
-
} catch (error) {
|
|
622
|
-
this.logger.error(`Failed to load data for ${tableName}:`, {
|
|
623
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1527
|
+
async deleteNamespaceValue(tableName, key) {
|
|
1528
|
+
if (this.bindings) {
|
|
1529
|
+
const binding = this.getBinding(tableName);
|
|
1530
|
+
await binding.delete(key);
|
|
1531
|
+
} else {
|
|
1532
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
1533
|
+
await this.client.kv.namespaces.values.delete(namespaceId, key, {
|
|
1534
|
+
account_id: this.accountId
|
|
624
1535
|
});
|
|
625
|
-
return null;
|
|
626
1536
|
}
|
|
627
1537
|
}
|
|
628
|
-
async
|
|
629
|
-
const thread = await this.load({ tableName: TABLE_THREADS, keys: { id: threadId } });
|
|
630
|
-
if (!thread) return null;
|
|
1538
|
+
async deleteKV(tableName, key) {
|
|
631
1539
|
try {
|
|
632
|
-
|
|
633
|
-
...thread,
|
|
634
|
-
createdAt: this.ensureDate(thread.createdAt),
|
|
635
|
-
updatedAt: this.ensureDate(thread.updatedAt),
|
|
636
|
-
metadata: this.ensureMetadata(thread.metadata)
|
|
637
|
-
};
|
|
1540
|
+
await this.deleteNamespaceValue(tableName, key);
|
|
638
1541
|
} catch (error) {
|
|
639
|
-
this.logger.error(`
|
|
640
|
-
|
|
641
|
-
});
|
|
642
|
-
return null;
|
|
1542
|
+
this.logger.error(`Failed to delete KV value for ${tableName}:${key}:`, error);
|
|
1543
|
+
throw new Error(`Failed to delete KV value: ${error.message}`);
|
|
643
1544
|
}
|
|
644
1545
|
}
|
|
645
|
-
async
|
|
1546
|
+
async listKV(tableName, options) {
|
|
646
1547
|
try {
|
|
647
|
-
|
|
648
|
-
const threads = await Promise.all(
|
|
649
|
-
keyList.map(async (keyObj) => {
|
|
650
|
-
try {
|
|
651
|
-
const data = await this.getKV(TABLE_THREADS, keyObj.name);
|
|
652
|
-
if (!data) return null;
|
|
653
|
-
const thread = typeof data === "string" ? JSON.parse(data) : data;
|
|
654
|
-
if (!thread || !thread.resourceId || thread.resourceId !== resourceId) return null;
|
|
655
|
-
return {
|
|
656
|
-
...thread,
|
|
657
|
-
createdAt: this.ensureDate(thread.createdAt),
|
|
658
|
-
updatedAt: this.ensureDate(thread.updatedAt),
|
|
659
|
-
metadata: this.ensureMetadata(thread.metadata)
|
|
660
|
-
};
|
|
661
|
-
} catch (error) {
|
|
662
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
663
|
-
this.logger.error(`Error processing thread from key ${keyObj.name}:`, { message });
|
|
664
|
-
return null;
|
|
665
|
-
}
|
|
666
|
-
})
|
|
667
|
-
);
|
|
668
|
-
return threads.filter((thread) => thread !== null);
|
|
1548
|
+
return await this.listNamespaceKeys(tableName, options);
|
|
669
1549
|
} catch (error) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return [];
|
|
1550
|
+
this.logger.error(`Failed to list KV for ${tableName}:`, error);
|
|
1551
|
+
throw new Error(`Failed to list KV: ${error.message}`);
|
|
673
1552
|
}
|
|
674
1553
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1554
|
+
};
|
|
1555
|
+
function transformScoreRow(row) {
|
|
1556
|
+
const deserialized = { ...row };
|
|
1557
|
+
deserialized.input = safelyParseJSON(row.input);
|
|
1558
|
+
deserialized.output = safelyParseJSON(row.output);
|
|
1559
|
+
deserialized.scorer = safelyParseJSON(row.scorer);
|
|
1560
|
+
deserialized.preprocessStepResult = safelyParseJSON(row.preprocessStepResult);
|
|
1561
|
+
deserialized.analyzeStepResult = safelyParseJSON(row.analyzeStepResult);
|
|
1562
|
+
deserialized.metadata = safelyParseJSON(row.metadata);
|
|
1563
|
+
deserialized.additionalContext = safelyParseJSON(row.additionalContext);
|
|
1564
|
+
deserialized.requestContext = safelyParseJSON(row.requestContext);
|
|
1565
|
+
deserialized.entity = safelyParseJSON(row.entity);
|
|
1566
|
+
return deserialized;
|
|
1567
|
+
}
|
|
1568
|
+
var ScoresStorageCloudflare = class extends ScoresStorage {
|
|
1569
|
+
operations;
|
|
1570
|
+
constructor({ operations }) {
|
|
1571
|
+
super();
|
|
1572
|
+
this.operations = operations;
|
|
684
1573
|
}
|
|
685
|
-
async
|
|
686
|
-
id,
|
|
687
|
-
title,
|
|
688
|
-
metadata
|
|
689
|
-
}) {
|
|
1574
|
+
async getScoreById({ id }) {
|
|
690
1575
|
try {
|
|
691
|
-
const
|
|
692
|
-
if (!
|
|
693
|
-
|
|
1576
|
+
const score = await this.operations.getKV(TABLE_SCORERS, id);
|
|
1577
|
+
if (!score) {
|
|
1578
|
+
return null;
|
|
694
1579
|
}
|
|
695
|
-
|
|
696
|
-
...thread,
|
|
697
|
-
title,
|
|
698
|
-
metadata: this.ensureMetadata({
|
|
699
|
-
...thread.metadata ?? {},
|
|
700
|
-
...metadata
|
|
701
|
-
}),
|
|
702
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
703
|
-
};
|
|
704
|
-
await this.insert({ tableName: TABLE_THREADS, record: updatedThread });
|
|
705
|
-
return updatedThread;
|
|
1580
|
+
return transformScoreRow(score);
|
|
706
1581
|
} catch (error) {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
1582
|
+
const mastraError = new MastraError(
|
|
1583
|
+
{
|
|
1584
|
+
id: "CLOUDFLARE_STORAGE_SCORES_GET_SCORE_BY_ID_FAILED",
|
|
1585
|
+
domain: ErrorDomain.STORAGE,
|
|
1586
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1587
|
+
text: `Failed to get score by id: ${id}`
|
|
1588
|
+
},
|
|
1589
|
+
error
|
|
1590
|
+
);
|
|
1591
|
+
this.logger.trackException(mastraError);
|
|
1592
|
+
this.logger.error(mastraError.toString());
|
|
1593
|
+
return null;
|
|
710
1594
|
}
|
|
711
1595
|
}
|
|
712
|
-
async
|
|
1596
|
+
async saveScore(score) {
|
|
1597
|
+
let parsedScore;
|
|
713
1598
|
try {
|
|
714
|
-
|
|
715
|
-
if (!thread) {
|
|
716
|
-
throw new Error(`Thread ${threadId} not found`);
|
|
717
|
-
}
|
|
718
|
-
const messageKeys = await this.listKV(TABLE_MESSAGES);
|
|
719
|
-
const threadMessageKeys = messageKeys.filter((key) => key.name.includes(`${TABLE_MESSAGES}:${threadId}:`));
|
|
720
|
-
await Promise.all([
|
|
721
|
-
// Delete message order
|
|
722
|
-
this.deleteKV(TABLE_MESSAGES, this.getThreadMessagesKey(threadId)),
|
|
723
|
-
// Delete all messages
|
|
724
|
-
...threadMessageKeys.map((key) => this.deleteKV(TABLE_MESSAGES, key.name)),
|
|
725
|
-
// Delete thread
|
|
726
|
-
this.deleteKV(TABLE_THREADS, this.getKey(TABLE_THREADS, { id: threadId }))
|
|
727
|
-
]);
|
|
1599
|
+
parsedScore = saveScorePayloadSchema.parse(score);
|
|
728
1600
|
} catch (error) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1601
|
+
throw new MastraError(
|
|
1602
|
+
{
|
|
1603
|
+
id: "CLOUDFLARE_STORAGE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
|
|
1604
|
+
domain: ErrorDomain.STORAGE,
|
|
1605
|
+
category: ErrorCategory.USER,
|
|
1606
|
+
details: { scoreId: score.id }
|
|
1607
|
+
},
|
|
1608
|
+
error
|
|
1609
|
+
);
|
|
732
1610
|
}
|
|
733
|
-
}
|
|
734
|
-
getMessageKey(threadId, messageId) {
|
|
735
1611
|
try {
|
|
736
|
-
|
|
1612
|
+
const id = crypto.randomUUID();
|
|
1613
|
+
const serializedRecord = {};
|
|
1614
|
+
for (const [key, value] of Object.entries(parsedScore)) {
|
|
1615
|
+
if (value !== null && value !== void 0) {
|
|
1616
|
+
if (typeof value === "object") {
|
|
1617
|
+
serializedRecord[key] = JSON.stringify(value);
|
|
1618
|
+
} else {
|
|
1619
|
+
serializedRecord[key] = value;
|
|
1620
|
+
}
|
|
1621
|
+
} else {
|
|
1622
|
+
serializedRecord[key] = null;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
serializedRecord.id = id;
|
|
1626
|
+
serializedRecord.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1627
|
+
serializedRecord.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1628
|
+
await this.operations.putKV({
|
|
1629
|
+
tableName: TABLE_SCORERS,
|
|
1630
|
+
key: id,
|
|
1631
|
+
value: serializedRecord
|
|
1632
|
+
});
|
|
1633
|
+
const scoreFromDb = await this.getScoreById({ id: score.id });
|
|
1634
|
+
return { score: scoreFromDb };
|
|
737
1635
|
} catch (error) {
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
1636
|
+
const mastraError = new MastraError(
|
|
1637
|
+
{
|
|
1638
|
+
id: "CLOUDFLARE_STORAGE_SCORES_SAVE_SCORE_FAILED",
|
|
1639
|
+
domain: ErrorDomain.STORAGE,
|
|
1640
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1641
|
+
text: `Failed to save score: ${score.id}`
|
|
1642
|
+
},
|
|
1643
|
+
error
|
|
1644
|
+
);
|
|
1645
|
+
this.logger.trackException(mastraError);
|
|
1646
|
+
this.logger.error(mastraError.toString());
|
|
1647
|
+
throw mastraError;
|
|
741
1648
|
}
|
|
742
1649
|
}
|
|
743
|
-
|
|
1650
|
+
async listScoresByScorerId({
|
|
1651
|
+
scorerId,
|
|
1652
|
+
entityId,
|
|
1653
|
+
entityType,
|
|
1654
|
+
source,
|
|
1655
|
+
pagination
|
|
1656
|
+
}) {
|
|
744
1657
|
try {
|
|
745
|
-
|
|
1658
|
+
const keys = await this.operations.listKV(TABLE_SCORERS);
|
|
1659
|
+
const scores = [];
|
|
1660
|
+
for (const { name: key } of keys) {
|
|
1661
|
+
const score = await this.operations.getKV(TABLE_SCORERS, key);
|
|
1662
|
+
if (entityId && score.entityId !== entityId) {
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
if (entityType && score.entityType !== entityType) {
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
if (source && score.source !== source) {
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (score && score.scorerId === scorerId) {
|
|
1672
|
+
scores.push(transformScoreRow(score));
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
scores.sort((a, b) => {
|
|
1676
|
+
const dateA = new Date(a.createdAt || 0).getTime();
|
|
1677
|
+
const dateB = new Date(b.createdAt || 0).getTime();
|
|
1678
|
+
return dateB - dateA;
|
|
1679
|
+
});
|
|
1680
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1681
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
1682
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1683
|
+
const total = scores.length;
|
|
1684
|
+
const end = perPageInput === false ? scores.length : start + perPage;
|
|
1685
|
+
const pagedScores = scores.slice(start, end);
|
|
1686
|
+
return {
|
|
1687
|
+
pagination: {
|
|
1688
|
+
total,
|
|
1689
|
+
page,
|
|
1690
|
+
perPage: perPageForResponse,
|
|
1691
|
+
hasMore: end < total
|
|
1692
|
+
},
|
|
1693
|
+
scores: pagedScores
|
|
1694
|
+
};
|
|
746
1695
|
} catch (error) {
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
1696
|
+
const mastraError = new MastraError(
|
|
1697
|
+
{
|
|
1698
|
+
id: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_SCORER_ID_FAILED",
|
|
1699
|
+
domain: ErrorDomain.STORAGE,
|
|
1700
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1701
|
+
text: `Failed to get scores by scorer id: ${scorerId}`
|
|
1702
|
+
},
|
|
1703
|
+
error
|
|
1704
|
+
);
|
|
1705
|
+
this.logger?.trackException(mastraError);
|
|
1706
|
+
this.logger?.error(mastraError.toString());
|
|
1707
|
+
return { pagination: { total: 0, page: 0, perPage: 100, hasMore: false }, scores: [] };
|
|
750
1708
|
}
|
|
751
1709
|
}
|
|
752
|
-
async
|
|
753
|
-
|
|
1710
|
+
async listScoresByRunId({
|
|
1711
|
+
runId,
|
|
1712
|
+
pagination
|
|
1713
|
+
}) {
|
|
754
1714
|
try {
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (
|
|
760
|
-
|
|
761
|
-
if (!message.createdAt) errors.push("createdAt is required");
|
|
762
|
-
if (errors.length > 0) {
|
|
763
|
-
throw new Error(`Invalid message at index ${index}: ${errors.join(", ")}`);
|
|
1715
|
+
const keys = await this.operations.listKV(TABLE_SCORERS);
|
|
1716
|
+
const scores = [];
|
|
1717
|
+
for (const { name: key } of keys) {
|
|
1718
|
+
const score = await this.operations.getKV(TABLE_SCORERS, key);
|
|
1719
|
+
if (score && score.runId === runId) {
|
|
1720
|
+
scores.push(transformScoreRow(score));
|
|
764
1721
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
};
|
|
1722
|
+
}
|
|
1723
|
+
scores.sort((a, b) => {
|
|
1724
|
+
const dateA = new Date(a.createdAt || 0).getTime();
|
|
1725
|
+
const dateB = new Date(b.createdAt || 0).getTime();
|
|
1726
|
+
return dateB - dateA;
|
|
771
1727
|
});
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
threadMessages.map(async (message) => {
|
|
788
|
-
const key = await this.getMessageKey(threadId, message.id);
|
|
789
|
-
const { _index, ...cleanMessage } = message;
|
|
790
|
-
const serializedMessage = {
|
|
791
|
-
...cleanMessage,
|
|
792
|
-
createdAt: this.serializeDate(cleanMessage.createdAt)
|
|
793
|
-
};
|
|
794
|
-
await this.putKV({ tableName: TABLE_MESSAGES, key, value: serializedMessage });
|
|
795
|
-
})
|
|
796
|
-
);
|
|
797
|
-
const orderKey = this.getThreadMessagesKey(threadId);
|
|
798
|
-
const entries = await this.updateSorting(threadMessages);
|
|
799
|
-
await this.updateSortedMessages(orderKey, entries);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
802
|
-
this.logger.error(`Error processing messages for thread ${threadId}: ${errorMessage}`);
|
|
803
|
-
throw error;
|
|
804
|
-
}
|
|
805
|
-
})
|
|
806
|
-
);
|
|
807
|
-
return validatedMessages.map(({ _index, ...message }) => message);
|
|
1728
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1729
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
1730
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1731
|
+
const total = scores.length;
|
|
1732
|
+
const end = perPageInput === false ? scores.length : start + perPage;
|
|
1733
|
+
const pagedScores = scores.slice(start, end);
|
|
1734
|
+
return {
|
|
1735
|
+
pagination: {
|
|
1736
|
+
total,
|
|
1737
|
+
page,
|
|
1738
|
+
perPage: perPageForResponse,
|
|
1739
|
+
hasMore: end < total
|
|
1740
|
+
},
|
|
1741
|
+
scores: pagedScores
|
|
1742
|
+
};
|
|
808
1743
|
} catch (error) {
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
1744
|
+
const mastraError = new MastraError(
|
|
1745
|
+
{
|
|
1746
|
+
id: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_RUN_ID_FAILED",
|
|
1747
|
+
domain: ErrorDomain.STORAGE,
|
|
1748
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1749
|
+
text: `Failed to get scores by run id: ${runId}`
|
|
1750
|
+
},
|
|
1751
|
+
error
|
|
1752
|
+
);
|
|
1753
|
+
this.logger.trackException(mastraError);
|
|
1754
|
+
this.logger.error(mastraError.toString());
|
|
1755
|
+
return { pagination: { total: 0, page: 0, perPage: 100, hasMore: false }, scores: [] };
|
|
812
1756
|
}
|
|
813
1757
|
}
|
|
814
|
-
async
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1758
|
+
async listScoresByEntityId({
|
|
1759
|
+
entityId,
|
|
1760
|
+
entityType,
|
|
1761
|
+
pagination
|
|
1762
|
+
}) {
|
|
1763
|
+
try {
|
|
1764
|
+
const keys = await this.operations.listKV(TABLE_SCORERS);
|
|
1765
|
+
const scores = [];
|
|
1766
|
+
for (const { name: key } of keys) {
|
|
1767
|
+
const score = await this.operations.getKV(TABLE_SCORERS, key);
|
|
1768
|
+
if (score && score.entityId === entityId && score.entityType === entityType) {
|
|
1769
|
+
scores.push(transformScoreRow(score));
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
scores.sort((a, b) => {
|
|
1773
|
+
const dateA = new Date(a.createdAt || 0).getTime();
|
|
1774
|
+
const dateB = new Date(b.createdAt || 0).getTime();
|
|
1775
|
+
return dateB - dateA;
|
|
1776
|
+
});
|
|
1777
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1778
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
1779
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1780
|
+
const total = scores.length;
|
|
1781
|
+
const end = perPageInput === false ? scores.length : start + perPage;
|
|
1782
|
+
const pagedScores = scores.slice(start, end);
|
|
1783
|
+
return {
|
|
1784
|
+
pagination: {
|
|
1785
|
+
total,
|
|
1786
|
+
page,
|
|
1787
|
+
perPage: perPageForResponse,
|
|
1788
|
+
hasMore: end < total
|
|
1789
|
+
},
|
|
1790
|
+
scores: pagedScores
|
|
1791
|
+
};
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
const mastraError = new MastraError(
|
|
1794
|
+
{
|
|
1795
|
+
id: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_ENTITY_ID_FAILED",
|
|
1796
|
+
domain: ErrorDomain.STORAGE,
|
|
1797
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1798
|
+
text: `Failed to get scores by entity id: ${entityId}, type: ${entityType}`
|
|
1799
|
+
},
|
|
1800
|
+
error
|
|
1801
|
+
);
|
|
1802
|
+
this.logger.trackException(mastraError);
|
|
1803
|
+
this.logger.error(mastraError.toString());
|
|
1804
|
+
return { pagination: { total: 0, page: 0, perPage: 100, hasMore: false }, scores: [] };
|
|
821
1805
|
}
|
|
822
|
-
|
|
823
|
-
|
|
1806
|
+
}
|
|
1807
|
+
async listScoresBySpan({
|
|
1808
|
+
traceId,
|
|
1809
|
+
spanId,
|
|
1810
|
+
pagination
|
|
1811
|
+
}) {
|
|
824
1812
|
try {
|
|
825
|
-
await
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
833
|
-
const messageOrder = await this.getFullOrder(threadMessagesKey);
|
|
834
|
-
const orderMap = new Map(messageOrder.map((id, index) => [id, index]));
|
|
835
|
-
messages.sort((a, b) => {
|
|
836
|
-
const indexA = orderMap.get(a.id);
|
|
837
|
-
const indexB = orderMap.get(b.id);
|
|
838
|
-
if (indexA !== void 0 && indexB !== void 0) return orderMap.get(a.id) - orderMap.get(b.id);
|
|
839
|
-
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
840
|
-
});
|
|
841
|
-
} catch (error) {
|
|
842
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
843
|
-
this.logger.warn(`Error sorting messages, falling back to creation time: ${errorMessage}`);
|
|
844
|
-
messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
1813
|
+
const keys = await this.operations.listKV(TABLE_SCORERS);
|
|
1814
|
+
const scores = [];
|
|
1815
|
+
for (const { name: key } of keys) {
|
|
1816
|
+
const score = await this.operations.getKV(TABLE_SCORERS, key);
|
|
1817
|
+
if (score && score.traceId === traceId && score.spanId === spanId) {
|
|
1818
|
+
scores.push(transformScoreRow(score));
|
|
1819
|
+
}
|
|
845
1820
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1821
|
+
scores.sort((a, b) => {
|
|
1822
|
+
const dateA = new Date(a.createdAt || 0).getTime();
|
|
1823
|
+
const dateB = new Date(b.createdAt || 0).getTime();
|
|
1824
|
+
return dateB - dateA;
|
|
1825
|
+
});
|
|
1826
|
+
const { page, perPage: perPageInput } = pagination;
|
|
1827
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
1828
|
+
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1829
|
+
const total = scores.length;
|
|
1830
|
+
const end = perPageInput === false ? scores.length : start + perPage;
|
|
1831
|
+
const pagedScores = scores.slice(start, end);
|
|
1832
|
+
return {
|
|
1833
|
+
pagination: {
|
|
1834
|
+
total,
|
|
1835
|
+
page,
|
|
1836
|
+
perPage: perPageForResponse,
|
|
1837
|
+
hasMore: end < total
|
|
1838
|
+
},
|
|
1839
|
+
scores: pagedScores
|
|
1840
|
+
};
|
|
850
1841
|
} catch (error) {
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
1842
|
+
const mastraError = new MastraError(
|
|
1843
|
+
{
|
|
1844
|
+
id: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_SPAN_FAILED",
|
|
1845
|
+
domain: ErrorDomain.STORAGE,
|
|
1846
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1847
|
+
text: `Failed to get scores by span: traceId=${traceId}, spanId=${spanId}`
|
|
1848
|
+
},
|
|
1849
|
+
error
|
|
1850
|
+
);
|
|
1851
|
+
this.logger?.trackException(mastraError);
|
|
1852
|
+
this.logger?.error(mastraError.toString());
|
|
1853
|
+
return { pagination: { total: 0, page: 0, perPage: 100, hasMore: false }, scores: [] };
|
|
854
1854
|
}
|
|
855
1855
|
}
|
|
1856
|
+
};
|
|
1857
|
+
var WorkflowsStorageCloudflare = class extends WorkflowsStorage {
|
|
1858
|
+
operations;
|
|
1859
|
+
constructor({ operations }) {
|
|
1860
|
+
super();
|
|
1861
|
+
this.operations = operations;
|
|
1862
|
+
}
|
|
856
1863
|
validateWorkflowParams(params) {
|
|
857
|
-
const {
|
|
858
|
-
if (!
|
|
1864
|
+
const { workflowName, runId } = params;
|
|
1865
|
+
if (!workflowName || !runId) {
|
|
859
1866
|
throw new Error("Invalid workflow snapshot parameters");
|
|
860
1867
|
}
|
|
861
1868
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
normalizedSteps[stepId] = {
|
|
871
|
-
status: step.status,
|
|
872
|
-
payload: step.payload || step.result,
|
|
873
|
-
error: step.error
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
return normalizedSteps;
|
|
1869
|
+
updateWorkflowResults({
|
|
1870
|
+
// workflowName,
|
|
1871
|
+
// runId,
|
|
1872
|
+
// stepId,
|
|
1873
|
+
// result,
|
|
1874
|
+
// requestContext,
|
|
1875
|
+
}) {
|
|
1876
|
+
throw new Error("Method not implemented.");
|
|
877
1877
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
steps: this.normalizeSteps(steps),
|
|
885
|
-
triggerData: data.context?.triggerData || {},
|
|
886
|
-
attempts: data.context?.attempts || {}
|
|
887
|
-
},
|
|
888
|
-
suspendedPaths: data.suspendedPaths || {},
|
|
889
|
-
activePaths: data.activePaths || [],
|
|
890
|
-
timestamp: data.timestamp || Date.now()
|
|
891
|
-
};
|
|
1878
|
+
updateWorkflowState({
|
|
1879
|
+
// workflowName,
|
|
1880
|
+
// runId,
|
|
1881
|
+
// opts,
|
|
1882
|
+
}) {
|
|
1883
|
+
throw new Error("Method not implemented.");
|
|
892
1884
|
}
|
|
893
1885
|
async persistWorkflowSnapshot(params) {
|
|
894
1886
|
try {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const normalizedState = this.normalizeWorkflowState(snapshot);
|
|
898
|
-
this.validateWorkflowState(normalizedState);
|
|
899
|
-
await this.insert({
|
|
1887
|
+
const { workflowName, runId, resourceId, snapshot } = params;
|
|
1888
|
+
await this.operations.putKV({
|
|
900
1889
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
901
|
-
|
|
902
|
-
|
|
1890
|
+
key: this.operations.getKey(TABLE_WORKFLOW_SNAPSHOT, { workflow_name: workflowName, run_id: runId }),
|
|
1891
|
+
value: {
|
|
903
1892
|
workflow_name: workflowName,
|
|
904
1893
|
run_id: runId,
|
|
905
|
-
|
|
1894
|
+
resourceId,
|
|
1895
|
+
snapshot: typeof snapshot === "string" ? snapshot : JSON.stringify(snapshot),
|
|
906
1896
|
createdAt: /* @__PURE__ */ new Date(),
|
|
907
1897
|
updatedAt: /* @__PURE__ */ new Date()
|
|
908
1898
|
}
|
|
909
1899
|
});
|
|
910
1900
|
} catch (error) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1901
|
+
throw new MastraError(
|
|
1902
|
+
{
|
|
1903
|
+
id: "CLOUDFLARE_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
|
|
1904
|
+
domain: ErrorDomain.STORAGE,
|
|
1905
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1906
|
+
text: `Error persisting workflow snapshot for workflow ${params.workflowName}, run ${params.runId}`,
|
|
1907
|
+
details: {
|
|
1908
|
+
workflowName: params.workflowName,
|
|
1909
|
+
runId: params.runId
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
error
|
|
1913
|
+
);
|
|
914
1914
|
}
|
|
915
1915
|
}
|
|
916
1916
|
async loadWorkflowSnapshot(params) {
|
|
917
1917
|
try {
|
|
918
1918
|
this.validateWorkflowParams(params);
|
|
919
|
-
const {
|
|
920
|
-
const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, {
|
|
921
|
-
const data = await this.getKV(TABLE_WORKFLOW_SNAPSHOT, key);
|
|
1919
|
+
const { workflowName, runId } = params;
|
|
1920
|
+
const key = this.operations.getKey(TABLE_WORKFLOW_SNAPSHOT, { workflow_name: workflowName, run_id: runId });
|
|
1921
|
+
const data = await this.operations.getKV(TABLE_WORKFLOW_SNAPSHOT, key);
|
|
922
1922
|
if (!data) return null;
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
return state;
|
|
926
|
-
} catch (error) {
|
|
927
|
-
this.logger.error("Error loading workflow snapshot:", {
|
|
928
|
-
error: error instanceof Error ? error.message : String(error)
|
|
929
|
-
});
|
|
930
|
-
return null;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
async batchInsert(input) {
|
|
934
|
-
if (!input.records || input.records.length === 0) return;
|
|
935
|
-
try {
|
|
936
|
-
await Promise.all(
|
|
937
|
-
input.records.map(async (record) => {
|
|
938
|
-
const key = this.getKey(input.tableName, record);
|
|
939
|
-
const processedRecord = {
|
|
940
|
-
...record,
|
|
941
|
-
createdAt: record.createdAt ? this.serializeDate(record.createdAt) : void 0,
|
|
942
|
-
updatedAt: record.updatedAt ? this.serializeDate(record.updatedAt) : void 0,
|
|
943
|
-
metadata: record.metadata ? JSON.stringify(record.metadata) : void 0
|
|
944
|
-
};
|
|
945
|
-
await this.putKV({ tableName: input.tableName, key, value: processedRecord });
|
|
946
|
-
})
|
|
947
|
-
);
|
|
1923
|
+
const snapshotData = typeof data.snapshot === "string" ? JSON.parse(data.snapshot) : data.snapshot;
|
|
1924
|
+
return snapshotData;
|
|
948
1925
|
} catch (error) {
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
toDate
|
|
962
|
-
}) {
|
|
963
|
-
try {
|
|
964
|
-
let keys;
|
|
965
|
-
if (this.bindings) {
|
|
966
|
-
keys = (await this.listKV(TABLE_TRACES))?.map((k) => k.name) || [];
|
|
967
|
-
} else {
|
|
968
|
-
const namespaceId = await this.getNamespaceId(TABLE_TRACES);
|
|
969
|
-
const result = await this.client.kv.namespaces.keys.list(namespaceId, {
|
|
970
|
-
prefix: "",
|
|
971
|
-
limit: 1e3,
|
|
972
|
-
account_id: this.accountId
|
|
973
|
-
});
|
|
974
|
-
keys = result.result?.map((k) => k.name) || [];
|
|
975
|
-
}
|
|
976
|
-
const traceRecords = await Promise.all(
|
|
977
|
-
keys.map(async (key) => {
|
|
978
|
-
const record = await this.getKV(TABLE_TRACES, key);
|
|
979
|
-
if (!record) return null;
|
|
980
|
-
return record;
|
|
981
|
-
})
|
|
982
|
-
);
|
|
983
|
-
let filteredTraces = traceRecords.filter(
|
|
984
|
-
(record) => record !== null && typeof record === "object"
|
|
1926
|
+
const mastraError = new MastraError(
|
|
1927
|
+
{
|
|
1928
|
+
id: "CLOUDFLARE_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
|
|
1929
|
+
domain: ErrorDomain.STORAGE,
|
|
1930
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1931
|
+
text: `Error loading workflow snapshot for workflow ${params.workflowName}, run ${params.runId}`,
|
|
1932
|
+
details: {
|
|
1933
|
+
workflowName: params.workflowName,
|
|
1934
|
+
runId: params.runId
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
error
|
|
985
1938
|
);
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (scope) {
|
|
990
|
-
filteredTraces = filteredTraces.filter((record) => record.scope === scope);
|
|
991
|
-
}
|
|
992
|
-
if (attributes) {
|
|
993
|
-
filteredTraces = filteredTraces.filter((record) => {
|
|
994
|
-
if (!record.attributes) return false;
|
|
995
|
-
const recordAttrs = this.parseJSON(record.attributes);
|
|
996
|
-
if (!recordAttrs) return false;
|
|
997
|
-
return Object.entries(attributes).every(([key, value]) => recordAttrs[key] === value);
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
if (fromDate) {
|
|
1001
|
-
filteredTraces = filteredTraces.filter((record) => new Date(record.createdAt).getTime() >= fromDate.getTime());
|
|
1002
|
-
}
|
|
1003
|
-
if (toDate) {
|
|
1004
|
-
filteredTraces = filteredTraces.filter((record) => new Date(record.createdAt).getTime() <= toDate.getTime());
|
|
1005
|
-
}
|
|
1006
|
-
filteredTraces.sort((a, b) => {
|
|
1007
|
-
const dateA = new Date(a.createdAt).getTime();
|
|
1008
|
-
const dateB = new Date(b.createdAt).getTime();
|
|
1009
|
-
return dateB - dateA;
|
|
1010
|
-
});
|
|
1011
|
-
const start = page * perPage;
|
|
1012
|
-
const end = start + perPage;
|
|
1013
|
-
const paginatedTraces = filteredTraces.slice(start, end);
|
|
1014
|
-
return paginatedTraces.map((record) => ({
|
|
1015
|
-
id: record.id,
|
|
1016
|
-
parentSpanId: record.parentSpanId,
|
|
1017
|
-
traceId: record.traceId,
|
|
1018
|
-
name: record.name,
|
|
1019
|
-
scope: record.scope,
|
|
1020
|
-
kind: record.kind,
|
|
1021
|
-
status: this.parseJSON(record.status),
|
|
1022
|
-
events: this.parseJSON(record.events) || [],
|
|
1023
|
-
links: this.parseJSON(record.links) || [],
|
|
1024
|
-
attributes: this.parseJSON(record?.attributes) || {},
|
|
1025
|
-
startTime: record.startTime,
|
|
1026
|
-
endTime: record.endTime,
|
|
1027
|
-
other: this.parseJSON(record.other) || {},
|
|
1028
|
-
createdAt: record.createdAt
|
|
1029
|
-
}));
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1032
|
-
this.logger.error("Failed to get traces:", { message });
|
|
1033
|
-
return [];
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
parseJSON(value) {
|
|
1037
|
-
if (typeof value === "string") {
|
|
1038
|
-
try {
|
|
1039
|
-
return JSON.parse(value);
|
|
1040
|
-
} catch {
|
|
1041
|
-
return value;
|
|
1042
|
-
}
|
|
1939
|
+
this.logger.trackException?.(mastraError);
|
|
1940
|
+
this.logger.error(mastraError.toString());
|
|
1941
|
+
return null;
|
|
1043
1942
|
}
|
|
1044
|
-
return value;
|
|
1045
|
-
}
|
|
1046
|
-
getEvalsByAgentName(_agentName, _type) {
|
|
1047
|
-
throw new Error("Method not implemented.");
|
|
1048
1943
|
}
|
|
1049
1944
|
parseWorkflowRun(row) {
|
|
1050
1945
|
let parsedSnapshot = row.snapshot;
|
|
@@ -1059,58 +1954,71 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
1059
1954
|
workflowName: row.workflow_name,
|
|
1060
1955
|
runId: row.run_id,
|
|
1061
1956
|
snapshot: parsedSnapshot,
|
|
1062
|
-
createdAt:
|
|
1063
|
-
updatedAt:
|
|
1957
|
+
createdAt: ensureDate(row.createdAt),
|
|
1958
|
+
updatedAt: ensureDate(row.updatedAt),
|
|
1064
1959
|
resourceId: row.resourceId
|
|
1065
1960
|
};
|
|
1066
1961
|
}
|
|
1067
1962
|
buildWorkflowSnapshotPrefix({
|
|
1068
|
-
namespace,
|
|
1069
1963
|
workflowName,
|
|
1070
1964
|
runId,
|
|
1071
1965
|
resourceId
|
|
1072
1966
|
}) {
|
|
1073
|
-
const prefix = this.namespacePrefix ? `${this.namespacePrefix}:` : "";
|
|
1967
|
+
const prefix = this.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
|
|
1074
1968
|
let key = `${prefix}${TABLE_WORKFLOW_SNAPSHOT}`;
|
|
1075
|
-
if (namespace) key += `:${namespace}`;
|
|
1076
1969
|
if (workflowName) key += `:${workflowName}`;
|
|
1077
1970
|
if (runId) key += `:${runId}`;
|
|
1078
1971
|
if (resourceId) key += `:${resourceId}`;
|
|
1079
|
-
if (!resourceId && (runId || workflowName || namespace)) key += ":";
|
|
1080
1972
|
return key;
|
|
1081
1973
|
}
|
|
1082
|
-
async
|
|
1083
|
-
namespace,
|
|
1974
|
+
async listWorkflowRuns({
|
|
1084
1975
|
workflowName,
|
|
1085
|
-
|
|
1086
|
-
|
|
1976
|
+
page = 0,
|
|
1977
|
+
perPage = 20,
|
|
1087
1978
|
resourceId,
|
|
1088
1979
|
fromDate,
|
|
1089
1980
|
toDate
|
|
1090
1981
|
} = {}) {
|
|
1091
1982
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1983
|
+
if (page < 0 || !Number.isInteger(page)) {
|
|
1984
|
+
throw new MastraError(
|
|
1985
|
+
{
|
|
1986
|
+
id: "CLOUDFLARE_STORE_INVALID_PAGE",
|
|
1987
|
+
domain: ErrorDomain.STORAGE,
|
|
1988
|
+
category: ErrorCategory.USER,
|
|
1989
|
+
details: { page }
|
|
1990
|
+
},
|
|
1991
|
+
new Error("page must be a non-negative integer")
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
const normalizedPerPage = normalizePerPage(perPage, 20);
|
|
1995
|
+
const offset = page * normalizedPerPage;
|
|
1996
|
+
const prefix = this.buildWorkflowSnapshotPrefix({ workflowName });
|
|
1997
|
+
const keyObjs = await this.operations.listKV(TABLE_WORKFLOW_SNAPSHOT, { prefix });
|
|
1094
1998
|
const runs = [];
|
|
1095
1999
|
for (const { name: key } of keyObjs) {
|
|
1096
2000
|
const parts = key.split(":");
|
|
1097
2001
|
const idx = parts.indexOf(TABLE_WORKFLOW_SNAPSHOT);
|
|
1098
|
-
if (idx === -1 || parts.length < idx +
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
if (
|
|
1103
|
-
|
|
1104
|
-
const data = await this.getKV(TABLE_WORKFLOW_SNAPSHOT, key);
|
|
2002
|
+
if (idx === -1 || parts.length < idx + 3) continue;
|
|
2003
|
+
const wfName = parts[idx + 1];
|
|
2004
|
+
const keyResourceId = parts.length > idx + 3 ? parts[idx + 3] : void 0;
|
|
2005
|
+
if (workflowName && wfName !== workflowName) continue;
|
|
2006
|
+
if (resourceId && keyResourceId !== resourceId) continue;
|
|
2007
|
+
const data = await this.operations.getKV(TABLE_WORKFLOW_SNAPSHOT, key);
|
|
1105
2008
|
if (!data) continue;
|
|
1106
2009
|
try {
|
|
1107
|
-
if (resourceId &&
|
|
1108
|
-
const createdAt =
|
|
2010
|
+
if (resourceId && !keyResourceId) continue;
|
|
2011
|
+
const createdAt = ensureDate(data.createdAt);
|
|
1109
2012
|
if (fromDate && createdAt && createdAt < fromDate) continue;
|
|
1110
2013
|
if (toDate && createdAt && createdAt > toDate) continue;
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
const run = this.parseWorkflowRun({
|
|
2014
|
+
const snapshotData = typeof data.snapshot === "string" ? JSON.parse(data.snapshot) : data.snapshot;
|
|
2015
|
+
const resourceIdToUse = keyResourceId || data.resourceId;
|
|
2016
|
+
const run = this.parseWorkflowRun({
|
|
2017
|
+
...data,
|
|
2018
|
+
workflow_name: wfName,
|
|
2019
|
+
resourceId: resourceIdToUse,
|
|
2020
|
+
snapshot: snapshotData
|
|
2021
|
+
});
|
|
1114
2022
|
runs.push(run);
|
|
1115
2023
|
} catch (err) {
|
|
1116
2024
|
this.logger.error("Failed to parse workflow snapshot:", { key, error: err });
|
|
@@ -1121,43 +2029,318 @@ var CloudflareStore = class extends MastraStorage {
|
|
|
1121
2029
|
const bDate = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
1122
2030
|
return bDate - aDate;
|
|
1123
2031
|
});
|
|
1124
|
-
const pagedRuns = runs.slice(offset, offset +
|
|
2032
|
+
const pagedRuns = runs.slice(offset, offset + normalizedPerPage);
|
|
1125
2033
|
return {
|
|
1126
2034
|
runs: pagedRuns,
|
|
1127
2035
|
total: runs.length
|
|
1128
2036
|
};
|
|
1129
2037
|
} catch (error) {
|
|
1130
|
-
const
|
|
1131
|
-
|
|
2038
|
+
const mastraError = new MastraError(
|
|
2039
|
+
{
|
|
2040
|
+
id: "CLOUDFLARE_STORAGE_LIST_WORKFLOW_RUNS_FAILED",
|
|
2041
|
+
domain: ErrorDomain.STORAGE,
|
|
2042
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2043
|
+
},
|
|
2044
|
+
error
|
|
2045
|
+
);
|
|
2046
|
+
this.logger.trackException?.(mastraError);
|
|
2047
|
+
this.logger.error(mastraError.toString());
|
|
1132
2048
|
return { runs: [], total: 0 };
|
|
1133
2049
|
}
|
|
1134
2050
|
}
|
|
1135
2051
|
async getWorkflowRunById({
|
|
1136
|
-
namespace,
|
|
1137
2052
|
runId,
|
|
1138
2053
|
workflowName
|
|
1139
2054
|
}) {
|
|
1140
2055
|
try {
|
|
1141
|
-
if (!runId || !workflowName
|
|
1142
|
-
throw new Error("runId, workflowName,
|
|
2056
|
+
if (!runId || !workflowName) {
|
|
2057
|
+
throw new Error("runId, workflowName, are required");
|
|
1143
2058
|
}
|
|
1144
|
-
const prefix = this.buildWorkflowSnapshotPrefix({
|
|
1145
|
-
const keyObjs = await this.listKV(TABLE_WORKFLOW_SNAPSHOT, { prefix });
|
|
2059
|
+
const prefix = this.buildWorkflowSnapshotPrefix({ workflowName, runId });
|
|
2060
|
+
const keyObjs = await this.operations.listKV(TABLE_WORKFLOW_SNAPSHOT, { prefix });
|
|
1146
2061
|
if (!keyObjs.length) return null;
|
|
1147
|
-
const
|
|
1148
|
-
|
|
2062
|
+
const exactKey = keyObjs.find((k) => {
|
|
2063
|
+
const parts = k.name.split(":");
|
|
2064
|
+
const idx = parts.indexOf(TABLE_WORKFLOW_SNAPSHOT);
|
|
2065
|
+
if (idx === -1 || parts.length < idx + 3) return false;
|
|
2066
|
+
const wfName = parts[idx + 1];
|
|
2067
|
+
const rId = parts[idx + 2];
|
|
2068
|
+
return wfName === workflowName && rId === runId;
|
|
2069
|
+
});
|
|
2070
|
+
if (!exactKey) return null;
|
|
2071
|
+
const data = await this.operations.getKV(TABLE_WORKFLOW_SNAPSHOT, exactKey.name);
|
|
1149
2072
|
if (!data) return null;
|
|
1150
|
-
const
|
|
1151
|
-
this.
|
|
1152
|
-
return this.parseWorkflowRun({ ...data, snapshot: state });
|
|
2073
|
+
const snapshotData = typeof data.snapshot === "string" ? JSON.parse(data.snapshot) : data.snapshot;
|
|
2074
|
+
return this.parseWorkflowRun({ ...data, snapshot: snapshotData });
|
|
1153
2075
|
} catch (error) {
|
|
1154
|
-
const
|
|
1155
|
-
|
|
2076
|
+
const mastraError = new MastraError(
|
|
2077
|
+
{
|
|
2078
|
+
id: "CLOUDFLARE_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED",
|
|
2079
|
+
domain: ErrorDomain.STORAGE,
|
|
2080
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2081
|
+
details: {
|
|
2082
|
+
workflowName,
|
|
2083
|
+
runId
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
error
|
|
2087
|
+
);
|
|
2088
|
+
this.logger.trackException?.(mastraError);
|
|
2089
|
+
this.logger.error(mastraError.toString());
|
|
1156
2090
|
return null;
|
|
1157
2091
|
}
|
|
1158
2092
|
}
|
|
2093
|
+
};
|
|
2094
|
+
|
|
2095
|
+
// src/storage/types.ts
|
|
2096
|
+
function isWorkersConfig(config) {
|
|
2097
|
+
return "bindings" in config;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/storage/index.ts
|
|
2101
|
+
var CloudflareStore = class extends MastraStorage {
|
|
2102
|
+
stores;
|
|
2103
|
+
client;
|
|
2104
|
+
accountId;
|
|
2105
|
+
namespacePrefix;
|
|
2106
|
+
bindings;
|
|
2107
|
+
validateWorkersConfig(config) {
|
|
2108
|
+
if (!isWorkersConfig(config)) {
|
|
2109
|
+
throw new Error("Invalid Workers API configuration");
|
|
2110
|
+
}
|
|
2111
|
+
if (!config.bindings) {
|
|
2112
|
+
throw new Error("KV bindings are required when using Workers Binding API");
|
|
2113
|
+
}
|
|
2114
|
+
const requiredTables = [TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCORERS];
|
|
2115
|
+
for (const table of requiredTables) {
|
|
2116
|
+
if (!(table in config.bindings)) {
|
|
2117
|
+
throw new Error(`Missing KV binding for table: ${table}`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
validateRestConfig(config) {
|
|
2122
|
+
if (isWorkersConfig(config)) {
|
|
2123
|
+
throw new Error("Invalid REST API configuration");
|
|
2124
|
+
}
|
|
2125
|
+
if (!config.accountId?.trim()) {
|
|
2126
|
+
throw new Error("accountId is required for REST API");
|
|
2127
|
+
}
|
|
2128
|
+
if (!config.apiToken?.trim()) {
|
|
2129
|
+
throw new Error("apiToken is required for REST API");
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
get supports() {
|
|
2133
|
+
const supports = super.supports;
|
|
2134
|
+
supports.listScoresBySpan = true;
|
|
2135
|
+
supports.resourceWorkingMemory = true;
|
|
2136
|
+
supports.selectByIncludeResourceScope = true;
|
|
2137
|
+
return supports;
|
|
2138
|
+
}
|
|
2139
|
+
constructor(config) {
|
|
2140
|
+
super({ name: "Cloudflare" });
|
|
2141
|
+
try {
|
|
2142
|
+
if (isWorkersConfig(config)) {
|
|
2143
|
+
this.validateWorkersConfig(config);
|
|
2144
|
+
this.bindings = config.bindings;
|
|
2145
|
+
this.namespacePrefix = config.keyPrefix?.trim() || "";
|
|
2146
|
+
this.logger.info("Using Cloudflare KV Workers Binding API");
|
|
2147
|
+
} else {
|
|
2148
|
+
this.validateRestConfig(config);
|
|
2149
|
+
this.accountId = config.accountId.trim();
|
|
2150
|
+
this.namespacePrefix = config.namespacePrefix?.trim() || "";
|
|
2151
|
+
this.client = new Cloudflare({
|
|
2152
|
+
apiToken: config.apiToken.trim()
|
|
2153
|
+
});
|
|
2154
|
+
this.logger.info("Using Cloudflare KV REST API");
|
|
2155
|
+
}
|
|
2156
|
+
const operations = new StoreOperationsCloudflare({
|
|
2157
|
+
accountId: this.accountId,
|
|
2158
|
+
client: this.client,
|
|
2159
|
+
namespacePrefix: this.namespacePrefix,
|
|
2160
|
+
bindings: this.bindings
|
|
2161
|
+
});
|
|
2162
|
+
const workflows = new WorkflowsStorageCloudflare({
|
|
2163
|
+
operations
|
|
2164
|
+
});
|
|
2165
|
+
const memory = new MemoryStorageCloudflare({
|
|
2166
|
+
operations
|
|
2167
|
+
});
|
|
2168
|
+
const scores = new ScoresStorageCloudflare({
|
|
2169
|
+
operations
|
|
2170
|
+
});
|
|
2171
|
+
this.stores = {
|
|
2172
|
+
operations,
|
|
2173
|
+
workflows,
|
|
2174
|
+
memory,
|
|
2175
|
+
scores
|
|
2176
|
+
};
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
throw new MastraError(
|
|
2179
|
+
{
|
|
2180
|
+
id: "CLOUDFLARE_STORAGE_INIT_FAILED",
|
|
2181
|
+
domain: ErrorDomain.STORAGE,
|
|
2182
|
+
category: ErrorCategory.THIRD_PARTY
|
|
2183
|
+
},
|
|
2184
|
+
error
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async createTable({
|
|
2189
|
+
tableName,
|
|
2190
|
+
schema
|
|
2191
|
+
}) {
|
|
2192
|
+
return this.stores.operations.createTable({ tableName, schema });
|
|
2193
|
+
}
|
|
2194
|
+
async alterTable(_args) {
|
|
2195
|
+
return this.stores.operations.alterTable(_args);
|
|
2196
|
+
}
|
|
2197
|
+
async clearTable({ tableName }) {
|
|
2198
|
+
return this.stores.operations.clearTable({ tableName });
|
|
2199
|
+
}
|
|
2200
|
+
async dropTable({ tableName }) {
|
|
2201
|
+
return this.stores.operations.dropTable({ tableName });
|
|
2202
|
+
}
|
|
2203
|
+
async insert({
|
|
2204
|
+
tableName,
|
|
2205
|
+
record
|
|
2206
|
+
}) {
|
|
2207
|
+
return this.stores.operations.insert({ tableName, record });
|
|
2208
|
+
}
|
|
2209
|
+
async load({ tableName, keys }) {
|
|
2210
|
+
return this.stores.operations.load({ tableName, keys });
|
|
2211
|
+
}
|
|
2212
|
+
async getThreadById({ threadId }) {
|
|
2213
|
+
return this.stores.memory.getThreadById({ threadId });
|
|
2214
|
+
}
|
|
2215
|
+
async saveThread({ thread }) {
|
|
2216
|
+
return this.stores.memory.saveThread({ thread });
|
|
2217
|
+
}
|
|
2218
|
+
async updateThread({
|
|
2219
|
+
id,
|
|
2220
|
+
title,
|
|
2221
|
+
metadata
|
|
2222
|
+
}) {
|
|
2223
|
+
return this.stores.memory.updateThread({ id, title, metadata });
|
|
2224
|
+
}
|
|
2225
|
+
async deleteThread({ threadId }) {
|
|
2226
|
+
return this.stores.memory.deleteThread({ threadId });
|
|
2227
|
+
}
|
|
2228
|
+
async saveMessages(args) {
|
|
2229
|
+
return this.stores.memory.saveMessages(args);
|
|
2230
|
+
}
|
|
2231
|
+
async getMessages({
|
|
2232
|
+
threadId,
|
|
2233
|
+
resourceId,
|
|
2234
|
+
selectBy
|
|
2235
|
+
}) {
|
|
2236
|
+
return this.stores.memory.getMessages({ threadId, resourceId, selectBy });
|
|
2237
|
+
}
|
|
2238
|
+
async updateWorkflowResults({
|
|
2239
|
+
workflowName,
|
|
2240
|
+
runId,
|
|
2241
|
+
stepId,
|
|
2242
|
+
result,
|
|
2243
|
+
requestContext
|
|
2244
|
+
}) {
|
|
2245
|
+
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
2246
|
+
}
|
|
2247
|
+
async updateWorkflowState({
|
|
2248
|
+
workflowName,
|
|
2249
|
+
runId,
|
|
2250
|
+
opts
|
|
2251
|
+
}) {
|
|
2252
|
+
return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
|
|
2253
|
+
}
|
|
2254
|
+
async listMessagesById({ messageIds }) {
|
|
2255
|
+
return this.stores.memory.listMessagesById({ messageIds });
|
|
2256
|
+
}
|
|
2257
|
+
async persistWorkflowSnapshot(params) {
|
|
2258
|
+
return this.stores.workflows.persistWorkflowSnapshot(params);
|
|
2259
|
+
}
|
|
2260
|
+
async loadWorkflowSnapshot(params) {
|
|
2261
|
+
return this.stores.workflows.loadWorkflowSnapshot(params);
|
|
2262
|
+
}
|
|
2263
|
+
async batchInsert(input) {
|
|
2264
|
+
return this.stores.operations.batchInsert(input);
|
|
2265
|
+
}
|
|
2266
|
+
async listWorkflowRuns({
|
|
2267
|
+
workflowName,
|
|
2268
|
+
perPage = 20,
|
|
2269
|
+
page = 0,
|
|
2270
|
+
resourceId,
|
|
2271
|
+
fromDate,
|
|
2272
|
+
toDate
|
|
2273
|
+
} = {}) {
|
|
2274
|
+
return this.stores.workflows.listWorkflowRuns({
|
|
2275
|
+
workflowName,
|
|
2276
|
+
perPage,
|
|
2277
|
+
page,
|
|
2278
|
+
resourceId,
|
|
2279
|
+
fromDate,
|
|
2280
|
+
toDate
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
async getWorkflowRunById({
|
|
2284
|
+
runId,
|
|
2285
|
+
workflowName
|
|
2286
|
+
}) {
|
|
2287
|
+
return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
|
|
2288
|
+
}
|
|
2289
|
+
async updateMessages(args) {
|
|
2290
|
+
return this.stores.memory.updateMessages(args);
|
|
2291
|
+
}
|
|
2292
|
+
async getScoreById({ id }) {
|
|
2293
|
+
return this.stores.scores.getScoreById({ id });
|
|
2294
|
+
}
|
|
2295
|
+
async saveScore(score) {
|
|
2296
|
+
return this.stores.scores.saveScore(score);
|
|
2297
|
+
}
|
|
2298
|
+
async listScoresByRunId({
|
|
2299
|
+
runId,
|
|
2300
|
+
pagination
|
|
2301
|
+
}) {
|
|
2302
|
+
return this.stores.scores.listScoresByRunId({ runId, pagination });
|
|
2303
|
+
}
|
|
2304
|
+
async listScoresByEntityId({
|
|
2305
|
+
entityId,
|
|
2306
|
+
entityType,
|
|
2307
|
+
pagination
|
|
2308
|
+
}) {
|
|
2309
|
+
return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
|
|
2310
|
+
}
|
|
2311
|
+
async listScoresByScorerId({
|
|
2312
|
+
scorerId,
|
|
2313
|
+
entityId,
|
|
2314
|
+
entityType,
|
|
2315
|
+
source,
|
|
2316
|
+
pagination
|
|
2317
|
+
}) {
|
|
2318
|
+
return this.stores.scores.listScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
|
|
2319
|
+
}
|
|
2320
|
+
async listScoresBySpan({
|
|
2321
|
+
traceId,
|
|
2322
|
+
spanId,
|
|
2323
|
+
pagination
|
|
2324
|
+
}) {
|
|
2325
|
+
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
|
|
2326
|
+
}
|
|
2327
|
+
async getResourceById({ resourceId }) {
|
|
2328
|
+
return this.stores.memory.getResourceById({ resourceId });
|
|
2329
|
+
}
|
|
2330
|
+
async saveResource({ resource }) {
|
|
2331
|
+
return this.stores.memory.saveResource({ resource });
|
|
2332
|
+
}
|
|
2333
|
+
async updateResource({
|
|
2334
|
+
resourceId,
|
|
2335
|
+
workingMemory,
|
|
2336
|
+
metadata
|
|
2337
|
+
}) {
|
|
2338
|
+
return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
|
|
2339
|
+
}
|
|
1159
2340
|
async close() {
|
|
1160
2341
|
}
|
|
1161
2342
|
};
|
|
1162
2343
|
|
|
1163
2344
|
export { CloudflareStore };
|
|
2345
|
+
//# sourceMappingURL=index.js.map
|
|
2346
|
+
//# sourceMappingURL=index.js.map
|