@mastra/cloudflare 1.0.0-beta.6 → 1.0.0-beta.8

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