@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/dist/index.js CHANGED
@@ -1,1516 +1,1597 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { MastraStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCORERS, StoreOperations, TABLE_TRACES, WorkflowsStorage, ensureDate, normalizePerPage, MemoryStorage, calculatePagination, serializeDate, TABLE_RESOURCES, ScoresStorage, safelyParseJSON } from '@mastra/core/storage';
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
- var MemoryStorageCloudflare = class extends MemoryStorage {
9
- operations;
10
- constructor({ operations }) {
11
- super();
12
- this.operations = operations;
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
- ensureMetadata(metadata) {
15
- if (!metadata) return void 0;
16
- return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
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
- * Summarizes message content without exposing raw data (for logging).
20
- * Returns type, length, and keys only to prevent PII leakage.
85
+ * Helper to safely parse data from KV storage
21
86
  */
22
- summarizeMessageContent(content) {
23
- if (!content) return { type: "undefined" };
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
- return {
34
- ...thread,
35
- createdAt: ensureDate(thread.createdAt),
36
- updatedAt: ensureDate(thread.updatedAt),
37
- metadata: this.ensureMetadata(thread.metadata)
38
- };
39
- } catch (error) {
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
- error
50
- );
51
- this.logger?.trackException(mastraError);
52
- this.logger?.error(mastraError.toString());
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 listThreadsByResourceId(args) {
57
- try {
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
- page,
91
- perPage: perPageForResponse,
92
- total: threads.length,
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 saveThread({ thread }) {
121
+ async createNamespace(namespaceName) {
109
122
  try {
110
- await this.operations.insert({ tableName: TABLE_THREADS, record: thread });
111
- return thread;
123
+ const response = await this.createNamespaceById(namespaceName);
124
+ return response.id;
112
125
  } catch (error) {
113
- throw new MastraError(
114
- {
115
- id: "CLOUDFLARE_STORAGE_SAVE_THREAD_FAILED",
116
- domain: ErrorDomain.STORAGE,
117
- category: ErrorCategory.THIRD_PARTY,
118
- details: {
119
- threadId: thread.id
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 updateThread({
127
- id,
128
- title,
129
- metadata
130
- }) {
131
- try {
132
- const thread = await this.getThreadById({ threadId: id });
133
- if (!thread) {
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
- getMessageKey(threadId, messageId) {
165
+ async getNamespaceIdByName(namespaceName) {
163
166
  try {
164
- return this.operations.getKey(TABLE_MESSAGES, { threadId, id: messageId });
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
- const message = error instanceof Error ? error.message : String(error);
167
- this.logger?.error(`Error getting message key for thread ${threadId} and message ${messageId}:`, { message });
168
- throw error;
171
+ this.logger.error(`Failed to get namespace ID for ${namespaceName}:`, error);
172
+ return null;
169
173
  }
170
174
  }
171
- getThreadMessagesKey(threadId) {
172
- try {
173
- return this.operations.getKey(TABLE_MESSAGES, { threadId, id: "messages" });
174
- } catch (error) {
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 deleteThread({ threadId }) {
182
+ async getNamespaceId(tableName) {
183
+ const prefix = this.namespacePrefix ? `${this.namespacePrefix}_` : "";
181
184
  try {
182
- const thread = await this.getThreadById({ threadId });
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
- throw new MastraError(
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 findMessageInAnyThread(messageId) {
191
+ async getNamespaceValue(tableName, key) {
211
192
  try {
212
- const prefix = this.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
213
- const threadKeys = await this.operations.listKV(TABLE_THREADS, { prefix: `${prefix}${TABLE_THREADS}` });
214
- for (const { name: threadKey } of threadKeys) {
215
- const threadId = threadKey.split(":").pop();
216
- if (!threadId || threadId === "messages") continue;
217
- const messageKey = this.getMessageKey(threadId, messageId);
218
- const message = await this.operations.getKV(TABLE_MESSAGES, messageKey);
219
- if (message) {
220
- return { ...message, threadId };
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
- this.logger?.error(`Error finding message ${messageId} in any thread:`, error);
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
- * Queue for serializing sorted order updates.
231
- * Updates the sorted order for a given key. This operation is eventually consistent.
232
- */
233
- updateQueue = /* @__PURE__ */ new Map();
234
- async updateSorting(threadMessages) {
235
- return threadMessages.map((msg) => ({
236
- message: msg,
237
- // Use _index if available, otherwise timestamp, matching Upstash
238
- score: msg._index !== void 0 ? msg._index : msg.createdAt.getTime()
239
- })).sort((a, b) => a.score - b.score).map((item) => ({
240
- id: item.message.id,
241
- score: item.score
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
- * Updates the sorted order for a given key. This operation is eventually consistent.
246
- * Note: Operations on the same orderKey are serialized using a queue to prevent
247
- * concurrent updates from conflicting with each other.
248
- */
249
- async updateSortedMessages(orderKey, newEntries) {
250
- const currentPromise = this.updateQueue.get(orderKey) || Promise.resolve();
251
- const nextPromise = currentPromise.then(async () => {
252
- try {
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
- const updatedOrder = Array.from(orderMap.values()).sort((a, b) => a.score - b.score);
259
- await this.operations.putKV({
260
- tableName: TABLE_MESSAGES,
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
- this.updateQueue.set(orderKey, nextPromise);
275
- return nextPromise;
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 getSortedMessages(orderKey) {
278
- const raw = await this.operations.getKV(TABLE_MESSAGES, orderKey);
279
- if (!raw) return [];
279
+ async validateRecord(record, tableName) {
280
280
  try {
281
- const arr = JSON.parse(typeof raw === "string" ? raw : JSON.stringify(raw));
282
- return Array.isArray(arr) ? arr : [];
283
- } catch (e) {
284
- this.logger?.error(`Error parsing order data for key ${orderKey}:`, { e });
285
- return [];
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 migrateMessage(messageId, fromThreadId, toThreadId) {
325
+ async insert({ tableName, record }) {
289
326
  try {
290
- const oldMessageKey = this.getMessageKey(fromThreadId, messageId);
291
- const message = await this.operations.getKV(TABLE_MESSAGES, oldMessageKey);
292
- if (!message) return;
293
- const updatedMessage = {
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
- this.logger?.error(`Error migrating message ${messageId} from ${fromThreadId} to ${toThreadId}:`, error);
311
- throw error;
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 saveMessages(args) {
315
- const { messages } = args;
316
- if (!Array.isArray(messages) || messages.length === 0) return { messages: [] };
345
+ async load({ tableName, keys }) {
317
346
  try {
318
- const validatedMessages = messages.map((message, index) => {
319
- const errors = [];
320
- if (!message.id) errors.push("id is required");
321
- if (!message.threadId) errors.push("threadId is required");
322
- if (!message.content) errors.push("content is required");
323
- if (!message.role) errors.push("role is required");
324
- if (!message.createdAt) errors.push("createdAt is required");
325
- if (message.resourceId === null || message.resourceId === void 0) errors.push("resourceId is required");
326
- if (errors.length > 0) {
327
- throw new Error(`Invalid message at index ${index}: ${errors.join(", ")}`);
328
- }
329
- return {
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
- const prepared = validatedMessages.map(
406
- ({ _index, ...message }) => ({ ...message, type: message.type !== "v2" ? message.type : void 0 })
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: "CLOUDFLARE_STORAGE_SAVE_MESSAGES_FAILED",
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
- async getRank(orderKey, id) {
422
- const order = await this.getSortedMessages(orderKey);
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 getRecentMessages(threadId, limit, messageIds) {
465
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
466
- if (limit <= 0) return;
395
+ async putNamespaceValue({
396
+ tableName,
397
+ key,
398
+ value,
399
+ metadata
400
+ }) {
467
401
  try {
468
- const threadMessagesKey = this.getThreadMessagesKey(threadId);
469
- const latestIds = await this.getLastN(threadMessagesKey, limit);
470
- latestIds.forEach((id) => messageIds.add(id));
471
- } catch {
472
- this.logger?.debug(`No message order found for thread ${threadId}, skipping latest messages`);
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 fetchAndParseMessagesFromMultipleThreads(messageIds, include, targetThreadId) {
476
- const messageIdToThreadId = /* @__PURE__ */ new Map();
477
- if (include) {
478
- for (const item of include) {
479
- if (item.threadId) {
480
- messageIdToThreadId.set(item.id, item.threadId);
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 listMessagesById({ messageIds }) {
517
- if (messageIds.length === 0) return { messages: [] };
434
+ async createTable({
435
+ tableName,
436
+ schema
437
+ }) {
518
438
  try {
519
- const messages = (await Promise.all(messageIds.map((id) => this.findMessageInAnyThread(id)))).filter(
520
- (result) => !!result
521
- );
522
- const prepared = messages.map(({ _index, ...message }) => ({
523
- ...message,
524
- ...message.type !== `v2` && { type: message.type },
525
- createdAt: ensureDate(message.createdAt)
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
- const mastraError = new MastraError(
447
+ throw new MastraError(
531
448
  {
532
- id: "CLOUDFLARE_STORAGE_LIST_MESSAGES_BY_ID_FAILED",
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
- messageIds: JSON.stringify(messageIds)
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 listMessages(args) {
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
- if (page < 0) {
564
- throw new MastraError(
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
- const mastraError = new MastraError(
467
+ throw new MastraError(
735
468
  {
736
- id: "CLOUDFLARE_STORAGE_LIST_MESSAGES_FAILED",
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
- threadId,
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 updateMessages(args) {
480
+ async dropTable({ tableName }) {
759
481
  try {
760
- const { messages } = args;
761
- const updatedMessages = [];
762
- for (const messageUpdate of messages) {
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: "CLOUDFLARE_STORAGE_UPDATE_MESSAGES_FAILED",
489
+ id: createStorageErrorId("CLOUDFLARE", "DROP_TABLE", "FAILED"),
859
490
  domain: ErrorDomain.STORAGE,
860
491
  category: ErrorCategory.THIRD_PARTY,
861
- text: "Failed to update messages"
492
+ details: {
493
+ tableName
494
+ }
862
495
  },
863
496
  error
864
497
  );
865
498
  }
866
499
  }
867
- async getResourceById({ resourceId }) {
500
+ async listNamespaceKeys(tableName, options) {
868
501
  try {
869
- const data = await this.operations.getKV(TABLE_RESOURCES, resourceId);
870
- if (!data) return null;
871
- const resource = typeof data === "string" ? JSON.parse(data) : data;
872
- return {
873
- ...resource,
874
- createdAt: ensureDate(resource.createdAt),
875
- updatedAt: ensureDate(resource.updatedAt),
876
- metadata: this.ensureMetadata(resource.metadata)
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
- const mastraError = new MastraError(
519
+ throw new MastraError(
880
520
  {
881
- id: "CLOUDFLARE_STORAGE_GET_RESOURCE_BY_ID_FAILED",
521
+ id: createStorageErrorId("CLOUDFLARE", "LIST_NAMESPACE_KEYS", "FAILED"),
882
522
  domain: ErrorDomain.STORAGE,
883
523
  category: ErrorCategory.THIRD_PARTY,
884
524
  details: {
885
- resourceId
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 saveResource({ resource }) {
896
- try {
897
- const resourceToSave = {
898
- ...resource,
899
- metadata: resource.metadata ? JSON.stringify(resource.metadata) : null
900
- };
901
- await this.operations.putKV({
902
- tableName: TABLE_RESOURCES,
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
- return resource;
541
+ }
542
+ }
543
+ async deleteKV(tableName, key) {
544
+ try {
545
+ await this.deleteNamespaceValue(tableName, key);
907
546
  } catch (error) {
908
- throw new MastraError(
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: "CLOUDFLARE_STORAGE_SAVE_RESOURCE_FAILED",
603
+ id: createStorageErrorId("CLOUDFLARE", "GET_THREAD_BY_ID", "FAILED"),
911
604
  domain: ErrorDomain.STORAGE,
912
605
  category: ErrorCategory.THIRD_PARTY,
913
606
  details: {
914
- resourceId: resource.id
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 updateResource({
922
- resourceId,
923
- workingMemory,
924
- metadata
925
- }) {
926
- const existingResource = await this.getResourceById({ resourceId });
927
- if (!existingResource) {
928
- const newResource = {
929
- id: resourceId,
930
- workingMemory,
931
- metadata: metadata || {},
932
- createdAt: /* @__PURE__ */ new Date(),
933
- updatedAt: /* @__PURE__ */ new Date()
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
- return this.saveResource({ resource: newResource });
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 clearTable({ tableName }) {
669
+ async saveThread({ thread }) {
973
670
  try {
974
- const keys = await this.listKV(tableName);
975
- if (keys.length > 0) {
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: "CLOUDFLARE_STORAGE_CLEAR_TABLE_FAILED",
676
+ id: createStorageErrorId("CLOUDFLARE", "SAVE_THREAD", "FAILED"),
982
677
  domain: ErrorDomain.STORAGE,
983
678
  category: ErrorCategory.THIRD_PARTY,
984
679
  details: {
985
- tableName
680
+ threadId: thread.id
986
681
  }
987
682
  },
988
683
  error
989
684
  );
990
685
  }
991
686
  }
992
- async dropTable({ tableName }) {
687
+ async updateThread({
688
+ id,
689
+ title,
690
+ metadata
691
+ }) {
993
692
  try {
994
- const keys = await this.listKV(tableName);
995
- if (keys.length > 0) {
996
- await Promise.all(keys.map((keyObj) => this.deleteKV(tableName, keyObj.name)));
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: "CLOUDFLARE_STORAGE_DROP_TABLE_FAILED",
711
+ id: createStorageErrorId("CLOUDFLARE", "UPDATE_THREAD", "FAILED"),
1002
712
  domain: ErrorDomain.STORAGE,
1003
713
  category: ErrorCategory.THIRD_PARTY,
1004
714
  details: {
1005
- tableName
715
+ threadId: id,
716
+ title
1006
717
  }
1007
718
  },
1008
719
  error
1009
720
  );
1010
721
  }
1011
722
  }
1012
- getBinding(tableName) {
1013
- if (!this.bindings) {
1014
- throw new Error(`Cannot use Workers API binding for ${tableName}: Store initialized with REST API configuration`);
1015
- }
1016
- const binding = this.bindings[tableName];
1017
- if (!binding) throw new Error(`No binding found for namespace ${tableName}`);
1018
- return binding;
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
- getSchemaKey(tableName) {
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
- const data = JSON.parse(text);
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.error("Failed to parse text:", { message, text });
1073
- return null;
737
+ this.logger?.error(`Error getting thread messages key for thread ${threadId}:`, { message });
738
+ throw error;
1074
739
  }
1075
740
  }
1076
- async createNamespaceById(title) {
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 response = await this.createNamespaceById(namespaceName);
1093
- return response.id;
1094
- } catch (error) {
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
- async getNamespaceIdByName(namespaceName) {
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 response = await this.listNamespaces();
1137
- const namespace = response.result.find((ns) => ns.title === namespaceName);
1138
- return namespace ? namespace.id : null;
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.error(`Failed to get namespace ID for ${namespaceName}:`, error);
797
+ this.logger?.error(`Error finding message ${messageId} in any thread:`, error);
1141
798
  return null;
1142
799
  }
1143
800
  }
1144
- async getOrCreateNamespaceId(namespaceName) {
1145
- let namespaceId = await this.getNamespaceIdByName(namespaceName);
1146
- if (!namespaceId) {
1147
- namespaceId = await this.createNamespace(namespaceName);
1148
- }
1149
- return namespaceId;
1150
- }
1151
- async getNamespaceId(tableName) {
1152
- const prefix = this.namespacePrefix ? `${this.namespacePrefix}_` : "";
1153
- try {
1154
- return await this.getOrCreateNamespaceId(`${prefix}${tableName}`);
1155
- } catch (error) {
1156
- this.logger.error("Error fetching namespace ID:", error);
1157
- throw new Error(`Failed to fetch namespace ID for table ${tableName}: ${error.message}`);
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
- async getNamespaceValue(tableName, key) {
1161
- try {
1162
- if (this.bindings) {
1163
- const binding = this.getBinding(tableName);
1164
- const result = await binding.getWithMetadata(key, "text");
1165
- if (!result) return null;
1166
- return JSON.stringify(result);
1167
- } else {
1168
- const namespaceId = await this.getNamespaceId(tableName);
1169
- const response = await this.client.kv.namespaces.values.get(namespaceId, key, {
1170
- account_id: this.accountId
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
- return await response.text();
1173
- }
1174
- } catch (error) {
1175
- if (error.message && error.message.includes("key not found")) {
1176
- return null;
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
- const message = error instanceof Error ? error.message : String(error);
1179
- this.logger.error(`Failed to get value for ${tableName} ${key}:`, { message });
1180
- throw error;
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 getTableSchema(tableName) {
849
+ async getSortedMessages(orderKey) {
850
+ const raw = await this.#db.getKV(TABLE_MESSAGES, orderKey);
851
+ if (!raw) return [];
1193
852
  try {
1194
- const schemaKey = this.getSchemaKey(tableName);
1195
- return await this.getKV(tableName, schemaKey);
1196
- } catch (error) {
1197
- const message = error instanceof Error ? error.message : String(error);
1198
- this.logger.error(`Failed to get schema for ${tableName}:`, { message });
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 validateAgainstSchema(record, schema) {
860
+ async migrateMessage(messageId, fromThreadId, toThreadId) {
1228
861
  try {
1229
- if (!schema || typeof schema !== "object" || schema.value === null) {
1230
- throw new Error("Invalid schema format");
1231
- }
1232
- for (const [columnName, column] of Object.entries(schema)) {
1233
- const value = record[columnName];
1234
- if (column.primaryKey && (value === void 0 || value === null)) {
1235
- throw new Error(`Missing primary key value for column ${columnName}`);
1236
- }
1237
- if (!this.validateColumnValue(value, column)) {
1238
- const valueType = value === null ? "null" : typeof value;
1239
- throw new Error(`Invalid value for column ${columnName}: expected ${column.type}, got ${valueType}`);
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
- const message = error instanceof Error ? error.message : String(error);
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 validateRecord(record, tableName) {
886
+ async saveMessages(args) {
887
+ const { messages } = args;
888
+ if (!Array.isArray(messages) || messages.length === 0) return { messages: [] };
1249
889
  try {
1250
- if (!record || typeof record !== "object") {
1251
- throw new Error("Record must be an object");
1252
- }
1253
- const recordTyped = record;
1254
- const schema = await this.getTableSchema(tableName);
1255
- if (schema) {
1256
- await this.validateAgainstSchema(recordTyped, schema);
1257
- return;
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
- switch (tableName) {
1260
- case TABLE_THREADS:
1261
- if (!("id" in recordTyped) || !("resourceId" in recordTyped) || !("title" in recordTyped)) {
1262
- throw new Error("Thread record missing required fields");
1263
- }
1264
- break;
1265
- case TABLE_MESSAGES:
1266
- if (!("id" in recordTyped) || !("threadId" in recordTyped) || !("content" in recordTyped) || !("role" in recordTyped)) {
1267
- throw new Error("Message record missing required fields");
1268
- }
1269
- break;
1270
- case TABLE_WORKFLOW_SNAPSHOT:
1271
- if (!("workflow_name" in recordTyped) || !("run_id" in recordTyped)) {
1272
- throw new Error("Workflow record missing required fields");
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
- break;
1275
- case TABLE_TRACES:
1276
- if (!("id" in recordTyped)) {
1277
- throw new Error("Trace record missing required fields");
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
- break;
1280
- case TABLE_SCORERS:
1281
- if (!("id" in recordTyped) || !("scorerId" in recordTyped)) {
1282
- throw new Error("Score record missing required fields");
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
- break;
1285
- default:
1286
- throw new Error(`Unknown table type: ${tableName}`);
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
- } catch (error) {
1289
- const message = error instanceof Error ? error.message : String(error);
1290
- this.logger.error(`Failed to validate record for ${tableName}:`, { message, record });
1291
- throw error;
1292
- }
1293
- }
1294
- async insert({ tableName, record }) {
1295
- try {
1296
- const key = this.getKey(tableName, record);
1297
- const processedRecord = { ...record };
1298
- await this.validateRecord(processedRecord, tableName);
1299
- await this.putKV({ tableName, key, value: processedRecord });
1300
- } catch (error) {
1301
- throw new MastraError(
1302
- {
1303
- id: "CLOUDFLARE_STORAGE_INSERT_FAILED",
1304
- domain: ErrorDomain.STORAGE,
1305
- category: ErrorCategory.THIRD_PARTY,
1306
- details: {
1307
- tableName
1308
- }
1309
- },
1310
- error
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
- async load({ tableName, keys }) {
1315
- try {
1316
- const key = this.getKey(tableName, keys);
1317
- const data = await this.getKV(tableName, key);
1318
- if (!data) return null;
1319
- return data;
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: "CLOUDFLARE_STORAGE_LOAD_FAILED",
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
- tableName
1319
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1320
+ resourceId: resourceId ?? ""
1328
1321
  }
1329
1322
  },
1330
1323
  error
1331
1324
  );
1332
- this.logger?.trackException(mastraError);
1333
- this.logger?.error(mastraError.toString());
1334
- return null;
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 batchInsert(input) {
1338
- if (!input.records || input.records.length === 0) return;
1336
+ async updateMessages(args) {
1339
1337
  try {
1340
- await Promise.all(
1341
- input.records.map(async (record) => {
1342
- const key = this.getKey(input.tableName, record);
1343
- await this.putKV({ tableName: input.tableName, key, value: record });
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: "CLOUDFLARE_STORAGE_BATCH_INSERT_FAILED",
1436
+ id: createStorageErrorId("CLOUDFLARE", "UPDATE_MESSAGES", "FAILED"),
1350
1437
  domain: ErrorDomain.STORAGE,
1351
1438
  category: ErrorCategory.THIRD_PARTY,
1352
- text: `Error in batch insert for table ${input.tableName}`,
1353
- details: {
1354
- tableName: input.tableName
1355
- }
1439
+ text: "Failed to update messages"
1356
1440
  },
1357
1441
  error
1358
1442
  );
1359
- }
1360
- }
1361
- /**
1362
- * Helper to safely serialize data for KV storage
1363
- */
1364
- safeSerialize(data) {
1365
- return typeof data === "string" ? data : JSON.stringify(data);
1366
- }
1367
- async putNamespaceValue({
1368
- tableName,
1369
- key,
1370
- value,
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
- throw new MastraError(
1457
+ const mastraError = new MastraError(
1420
1458
  {
1421
- id: "CLOUDFLARE_STORAGE_CREATE_TABLE_FAILED",
1459
+ id: createStorageErrorId("CLOUDFLARE", "GET_RESOURCE_BY_ID", "FAILED"),
1422
1460
  domain: ErrorDomain.STORAGE,
1423
1461
  category: ErrorCategory.THIRD_PARTY,
1424
1462
  details: {
1425
- tableName
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 listNamespaceKeys(tableName, options) {
1473
+ async saveResource({ resource }) {
1433
1474
  try {
1434
- if (this.bindings) {
1435
- const binding = this.getBinding(tableName);
1436
- const response = await binding.list({
1437
- limit: options?.limit || 1e3,
1438
- prefix: options?.prefix
1439
- });
1440
- return response.keys;
1441
- } else {
1442
- const namespaceId = await this.getNamespaceId(tableName);
1443
- const response = await this.client.kv.namespaces.keys.list(namespaceId, {
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: "CLOUDFLARE_STORAGE_LIST_NAMESPACE_KEYS_FAILED",
1488
+ id: createStorageErrorId("CLOUDFLARE", "SAVE_RESOURCE", "FAILED"),
1454
1489
  domain: ErrorDomain.STORAGE,
1455
1490
  category: ErrorCategory.THIRD_PARTY,
1456
1491
  details: {
1457
- tableName
1492
+ resourceId: resource.id
1458
1493
  }
1459
1494
  },
1460
1495
  error
1461
1496
  );
1462
1497
  }
1463
1498
  }
1464
- async deleteNamespaceValue(tableName, key) {
1465
- if (this.bindings) {
1466
- const binding = this.getBinding(tableName);
1467
- await binding.delete(key);
1468
- } else {
1469
- const namespaceId = await this.getNamespaceId(tableName);
1470
- await this.client.kv.namespaces.values.delete(namespaceId, key, {
1471
- account_id: this.accountId
1472
- });
1473
- }
1474
- }
1475
- async deleteKV(tableName, key) {
1476
- try {
1477
- await this.deleteNamespaceValue(tableName, key);
1478
- } catch (error) {
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 listKV(tableName, options) {
1527
+ async deleteMessages(messageIds) {
1528
+ if (messageIds.length === 0) return;
1484
1529
  try {
1485
- return await this.listNamespaceKeys(tableName, options);
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
- this.logger.error(`Failed to list KV for ${tableName}:`, error);
1488
- throw new Error(`Failed to list KV: ${error.message}`);
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
- const deserialized = { ...row };
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
- operations;
1507
- constructor({ operations }) {
1582
+ #db;
1583
+ constructor(config) {
1508
1584
  super();
1509
- this.operations = operations;
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.operations.getKV(TABLE_SCORERS, id);
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: "CLOUDFLARE_STORAGE_SCORES_GET_SCORE_BY_ID_FAILED",
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.trackException(mastraError);
1529
- this.logger.error(mastraError.toString());
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: "CLOUDFLARE_STORAGE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
1621
+ id: createStorageErrorId("CLOUDFLARE", "SAVE_SCORE", "VALIDATION_FAILED"),
1541
1622
  domain: ErrorDomain.STORAGE,
1542
1623
  category: ErrorCategory.USER,
1543
- details: { scoreId: score.id }
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 = (/* @__PURE__ */ new Date()).toISOString();
1564
- serializedRecord.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1565
- await this.operations.putKV({
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
- const scoreFromDb = await this.getScoreById({ id: score.id });
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: "CLOUDFLARE_STORAGE_SCORES_SAVE_SCORE_FAILED",
1662
+ id: createStorageErrorId("CLOUDFLARE", "SAVE_SCORE", "FAILED"),
1576
1663
  domain: ErrorDomain.STORAGE,
1577
1664
  category: ErrorCategory.THIRD_PARTY,
1578
- text: `Failed to save score: ${score.id}`
1665
+ details: { id }
1579
1666
  },
1580
1667
  error
1581
1668
  );
1582
- this.logger.trackException(mastraError);
1583
- this.logger.error(mastraError.toString());
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.operations.listKV(TABLE_SCORERS);
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.operations.getKV(TABLE_SCORERS, key);
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: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_SCORER_ID_FAILED",
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.operations.listKV(TABLE_SCORERS);
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.operations.getKV(TABLE_SCORERS, key);
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: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_RUN_ID_FAILED",
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.trackException(mastraError);
1691
- this.logger.error(mastraError.toString());
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.operations.listKV(TABLE_SCORERS);
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.operations.getKV(TABLE_SCORERS, key);
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: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_ENTITY_ID_FAILED",
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.trackException(mastraError);
1740
- this.logger.error(mastraError.toString());
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.operations.listKV(TABLE_SCORERS);
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.operations.getKV(TABLE_SCORERS, key);
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: "CLOUDFLARE_STORAGE_SCORES_GET_SCORES_BY_SPAN_FAILED",
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
- operations;
1796
- constructor({ operations }) {
1882
+ #db;
1883
+ constructor(config) {
1797
1884
  super();
1798
- this.operations = operations;
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
- await this.operations.putKV({
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: this.operations.getKey(TABLE_WORKFLOW_SNAPSHOT, { workflow_name: workflowName, run_id: runId }),
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: /* @__PURE__ */ new Date(),
1834
- updatedAt: /* @__PURE__ */ new Date()
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: "CLOUDFLARE_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
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.operations.getKey(TABLE_WORKFLOW_SNAPSHOT, { workflow_name: workflowName, run_id: runId });
1858
- const data = await this.operations.getKV(TABLE_WORKFLOW_SNAPSHOT, key);
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: "CLOUDFLARE_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
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.operations.namespacePrefix ? `${this.operations.namespacePrefix}:` : "";
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: "CLOUDFLARE_STORE_INVALID_PAGE",
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.operations.listKV(TABLE_WORKFLOW_SNAPSHOT, { prefix });
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
- if (resourceId && keyResourceId !== resourceId) continue;
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
- if (resourceId && !keyResourceId) continue;
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: resourceIdToUse,
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: "CLOUDFLARE_STORAGE_LIST_WORKFLOW_RUNS_FAILED",
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.operations.listKV(TABLE_WORKFLOW_SNAPSHOT, { prefix });
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.operations.getKV(TABLE_WORKFLOW_SNAPSHOT, exactKey.name);
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: "CLOUDFLARE_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED",
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: "CLOUDFLARE_STORAGE_INIT_FAILED",
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