@mastra/cloudflare 0.0.0-error-handler-fix-20251020202607 → 0.0.0-execa-dynamic-import-20260304221256

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