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