@mastra/cloudflare 0.0.0-commonjs-20250414101718
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/LICENSE.md +46 -0
- package/README.md +0 -0
- package/dist/_tsup-dts-rollup.d.cts +237 -0
- package/dist/_tsup-dts-rollup.d.ts +237 -0
- package/dist/index.cjs +1011 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1005 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var storage = require('@mastra/core/storage');
|
|
4
|
+
var Cloudflare = require('cloudflare');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var Cloudflare__default = /*#__PURE__*/_interopDefault(Cloudflare);
|
|
9
|
+
|
|
10
|
+
// src/storage/index.ts
|
|
11
|
+
|
|
12
|
+
// src/storage/types.ts
|
|
13
|
+
function isWorkersConfig(config) {
|
|
14
|
+
return "bindings" in config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/storage/index.ts
|
|
18
|
+
var CloudflareStore = class extends storage.MastraStorage {
|
|
19
|
+
client;
|
|
20
|
+
accountId;
|
|
21
|
+
namespacePrefix;
|
|
22
|
+
bindings;
|
|
23
|
+
validateWorkersConfig(config) {
|
|
24
|
+
if (!isWorkersConfig(config)) {
|
|
25
|
+
throw new Error("Invalid Workers API configuration");
|
|
26
|
+
}
|
|
27
|
+
if (!config.bindings) {
|
|
28
|
+
throw new Error("KV bindings are required when using Workers Binding API");
|
|
29
|
+
}
|
|
30
|
+
const requiredTables = [storage.TABLE_THREADS, storage.TABLE_MESSAGES, storage.TABLE_WORKFLOW_SNAPSHOT, storage.TABLE_EVALS, storage.TABLE_TRACES];
|
|
31
|
+
for (const table of requiredTables) {
|
|
32
|
+
if (!(table in config.bindings)) {
|
|
33
|
+
throw new Error(`Missing KV binding for table: ${table}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
validateRestConfig(config) {
|
|
38
|
+
if (isWorkersConfig(config)) {
|
|
39
|
+
throw new Error("Invalid REST API configuration");
|
|
40
|
+
}
|
|
41
|
+
if (!config.accountId?.trim()) {
|
|
42
|
+
throw new Error("accountId is required for REST API");
|
|
43
|
+
}
|
|
44
|
+
if (!config.apiToken?.trim()) {
|
|
45
|
+
throw new Error("apiToken is required for REST API");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
constructor(config) {
|
|
49
|
+
super({ name: "Cloudflare" });
|
|
50
|
+
try {
|
|
51
|
+
if (isWorkersConfig(config)) {
|
|
52
|
+
this.validateWorkersConfig(config);
|
|
53
|
+
this.bindings = config.bindings;
|
|
54
|
+
this.namespacePrefix = config.keyPrefix?.trim() || "";
|
|
55
|
+
this.logger.info("Using Cloudflare KV Workers Binding API");
|
|
56
|
+
} else {
|
|
57
|
+
this.validateRestConfig(config);
|
|
58
|
+
this.accountId = config.accountId.trim();
|
|
59
|
+
this.namespacePrefix = config.namespacePrefix?.trim() || "";
|
|
60
|
+
this.client = new Cloudflare__default.default({
|
|
61
|
+
apiToken: config.apiToken.trim()
|
|
62
|
+
});
|
|
63
|
+
this.logger.info("Using Cloudflare KV REST API");
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.logger.error("Failed to initialize CloudflareStore:", { error });
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
getBinding(tableName) {
|
|
71
|
+
if (!this.bindings) {
|
|
72
|
+
throw new Error(`Cannot use Workers API binding for ${tableName}: Store initialized with REST API configuration`);
|
|
73
|
+
}
|
|
74
|
+
const binding = this.bindings[tableName];
|
|
75
|
+
if (!binding) throw new Error(`No binding found for namespace ${tableName}`);
|
|
76
|
+
return binding;
|
|
77
|
+
}
|
|
78
|
+
async listNamespaces() {
|
|
79
|
+
if (this.bindings) {
|
|
80
|
+
return {
|
|
81
|
+
result: Object.keys(this.bindings).map((name) => ({
|
|
82
|
+
id: name,
|
|
83
|
+
title: name,
|
|
84
|
+
supports_url_encoding: true
|
|
85
|
+
}))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return await this.client.kv.namespaces.list({ account_id: this.accountId });
|
|
89
|
+
}
|
|
90
|
+
async getNamespaceValue(tableName, key) {
|
|
91
|
+
try {
|
|
92
|
+
if (this.bindings) {
|
|
93
|
+
const binding = this.getBinding(tableName);
|
|
94
|
+
const result = await binding.getWithMetadata(key, "text");
|
|
95
|
+
if (!result) return null;
|
|
96
|
+
return JSON.stringify(result);
|
|
97
|
+
} else {
|
|
98
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
99
|
+
const response = await this.client.kv.namespaces.values.get(namespaceId, key, {
|
|
100
|
+
account_id: this.accountId
|
|
101
|
+
});
|
|
102
|
+
return await response.text();
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error.message && error.message.includes("key not found")) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
this.logger.error(`Failed to get value for ${tableName} ${key}:`, { error });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async putNamespaceValue({
|
|
113
|
+
tableName,
|
|
114
|
+
key,
|
|
115
|
+
value,
|
|
116
|
+
metadata
|
|
117
|
+
}) {
|
|
118
|
+
try {
|
|
119
|
+
const serializedValue = this.safeSerialize(value);
|
|
120
|
+
const serializedMetadata = metadata ? this.safeSerialize(metadata) : "";
|
|
121
|
+
if (this.bindings) {
|
|
122
|
+
const binding = this.getBinding(tableName);
|
|
123
|
+
await binding.put(key, serializedValue, { metadata: serializedMetadata });
|
|
124
|
+
} else {
|
|
125
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
126
|
+
await this.client.kv.namespaces.values.update(namespaceId, key, {
|
|
127
|
+
account_id: this.accountId,
|
|
128
|
+
value: serializedValue,
|
|
129
|
+
metadata: serializedMetadata
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.logger.error(`Failed to put value for ${tableName} ${key}:`, { error });
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async deleteNamespaceValue(tableName, key) {
|
|
138
|
+
if (this.bindings) {
|
|
139
|
+
const binding = this.getBinding(tableName);
|
|
140
|
+
await binding.delete(key);
|
|
141
|
+
} else {
|
|
142
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
143
|
+
await this.client.kv.namespaces.values.delete(namespaceId, key, {
|
|
144
|
+
account_id: this.accountId
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async listNamespaceKeys(tableName, options) {
|
|
149
|
+
try {
|
|
150
|
+
if (this.bindings) {
|
|
151
|
+
const binding = this.getBinding(tableName);
|
|
152
|
+
const response = await binding.list({
|
|
153
|
+
limit: options?.limit || 1e3,
|
|
154
|
+
prefix: options?.prefix
|
|
155
|
+
});
|
|
156
|
+
return response.keys;
|
|
157
|
+
} else {
|
|
158
|
+
const namespaceId = await this.getNamespaceId(tableName);
|
|
159
|
+
const response = await this.client.kv.namespaces.keys.list(namespaceId, {
|
|
160
|
+
account_id: this.accountId,
|
|
161
|
+
limit: options?.limit || 1e3,
|
|
162
|
+
prefix: options?.prefix
|
|
163
|
+
});
|
|
164
|
+
return response.result;
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
this.logger.error(`Failed to list keys for ${tableName}:`, error);
|
|
168
|
+
throw new Error(`Failed to list keys: ${error.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async createNamespaceById(title) {
|
|
172
|
+
if (this.bindings) {
|
|
173
|
+
return {
|
|
174
|
+
id: title,
|
|
175
|
+
// Use title as ID since that's what we need
|
|
176
|
+
title,
|
|
177
|
+
supports_url_encoding: true
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return await this.client.kv.namespaces.create({
|
|
181
|
+
account_id: this.accountId,
|
|
182
|
+
title
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async getNamespaceIdByName(namespaceName) {
|
|
186
|
+
try {
|
|
187
|
+
const response = await this.listNamespaces();
|
|
188
|
+
const namespace = response.result.find((ns) => ns.title === namespaceName);
|
|
189
|
+
return namespace ? namespace.id : null;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.logger.error(`Failed to get namespace ID for ${namespaceName}:`, error);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async createNamespace(namespaceName) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await this.createNamespaceById(namespaceName);
|
|
198
|
+
return response.id;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error.message && error.message.includes("already exists")) {
|
|
201
|
+
const namespaces = await this.listNamespaces();
|
|
202
|
+
const namespace = namespaces.result.find((ns) => ns.title === namespaceName);
|
|
203
|
+
if (namespace) return namespace.id;
|
|
204
|
+
}
|
|
205
|
+
this.logger.error("Error creating namespace:", error);
|
|
206
|
+
throw new Error(`Failed to create namespace ${namespaceName}: ${error.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async getOrCreateNamespaceId(namespaceName) {
|
|
210
|
+
let namespaceId = await this.getNamespaceIdByName(namespaceName);
|
|
211
|
+
if (!namespaceId) {
|
|
212
|
+
namespaceId = await this.createNamespace(namespaceName);
|
|
213
|
+
}
|
|
214
|
+
return namespaceId;
|
|
215
|
+
}
|
|
216
|
+
async getNamespaceId(tableName) {
|
|
217
|
+
const prefix = this.namespacePrefix ? `${this.namespacePrefix}_` : "";
|
|
218
|
+
try {
|
|
219
|
+
if (tableName === storage.TABLE_MESSAGES || tableName === storage.TABLE_THREADS) {
|
|
220
|
+
return await this.getOrCreateNamespaceId(`${prefix}mastra_threads`);
|
|
221
|
+
} else if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
|
|
222
|
+
return await this.getOrCreateNamespaceId(`${prefix}mastra_workflows`);
|
|
223
|
+
} else {
|
|
224
|
+
return await this.getOrCreateNamespaceId(`${prefix}mastra_evals`);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
this.logger.error("Error fetching namespace ID:", error);
|
|
228
|
+
throw new Error(`Failed to fetch namespace ID for table ${tableName}: ${error.message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Helper to safely serialize data for KV storage
|
|
233
|
+
*/
|
|
234
|
+
safeSerialize(data) {
|
|
235
|
+
return typeof data === "string" ? data : JSON.stringify(data);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Helper to safely parse data from KV storage
|
|
239
|
+
*/
|
|
240
|
+
safeParse(text) {
|
|
241
|
+
if (!text) return null;
|
|
242
|
+
try {
|
|
243
|
+
const data = JSON.parse(text);
|
|
244
|
+
if (data && typeof data === "object" && "value" in data) {
|
|
245
|
+
if (typeof data.value === "string") {
|
|
246
|
+
try {
|
|
247
|
+
return JSON.parse(data.value);
|
|
248
|
+
} catch {
|
|
249
|
+
return data.value;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return data;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
this.logger.error("Failed to parse text:", { error, text });
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async putKV({
|
|
261
|
+
tableName,
|
|
262
|
+
key,
|
|
263
|
+
value,
|
|
264
|
+
metadata
|
|
265
|
+
}) {
|
|
266
|
+
try {
|
|
267
|
+
await this.putNamespaceValue({ tableName, key, value, metadata });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
this.logger.error(`Failed to put KV value for ${tableName}:${key}:`, error);
|
|
270
|
+
throw new Error(`Failed to put KV value: ${error.message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async getKV(tableName, key) {
|
|
274
|
+
try {
|
|
275
|
+
const text = await this.getNamespaceValue(tableName, key);
|
|
276
|
+
return this.safeParse(text);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.logger.error(`Failed to get KV value for ${tableName}:${key}:`, error);
|
|
279
|
+
throw new Error(`Failed to get KV value: ${error.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async deleteKV(tableName, key) {
|
|
283
|
+
try {
|
|
284
|
+
await this.deleteNamespaceValue(tableName, key);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
this.logger.error(`Failed to delete KV value for ${tableName}:${key}:`, error);
|
|
287
|
+
throw new Error(`Failed to delete KV value: ${error.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async listKV(tableName) {
|
|
291
|
+
try {
|
|
292
|
+
return await this.listNamespaceKeys(tableName);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
this.logger.error(`Failed to list KV for ${tableName}:`, error);
|
|
295
|
+
throw new Error(`Failed to list KV: ${error.message}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/*---------------------------------------------------------------------------
|
|
299
|
+
Sorted set simulation helpers for message ordering.
|
|
300
|
+
We store an array of objects { id, score } as JSON under a dedicated key.
|
|
301
|
+
---------------------------------------------------------------------------*/
|
|
302
|
+
async getSortedMessages(orderKey) {
|
|
303
|
+
const raw = await this.getKV(storage.TABLE_MESSAGES, orderKey);
|
|
304
|
+
if (!raw) return [];
|
|
305
|
+
try {
|
|
306
|
+
const arr = JSON.parse(typeof raw === "string" ? raw : JSON.stringify(raw));
|
|
307
|
+
return Array.isArray(arr) ? arr : [];
|
|
308
|
+
} catch (e) {
|
|
309
|
+
this.logger.error(`Error parsing order data for key ${orderKey}:`, { e });
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async updateSorting(threadMessages) {
|
|
314
|
+
return threadMessages.map((msg) => ({
|
|
315
|
+
message: msg,
|
|
316
|
+
// Use _index if available, otherwise timestamp, matching Upstash
|
|
317
|
+
score: msg._index !== void 0 ? msg._index : msg.createdAt.getTime()
|
|
318
|
+
})).sort((a, b) => a.score - b.score).map((item) => ({
|
|
319
|
+
id: item.message.id,
|
|
320
|
+
score: item.score
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
async getIncludedMessagesWithContext(threadId, include, messageIds) {
|
|
324
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
325
|
+
await Promise.all(
|
|
326
|
+
include.map(async (item) => {
|
|
327
|
+
messageIds.add(item.id);
|
|
328
|
+
if (!item.withPreviousMessages && !item.withNextMessages) return;
|
|
329
|
+
const rank = await this.getRank(threadMessagesKey, item.id);
|
|
330
|
+
if (rank === null) return;
|
|
331
|
+
if (item.withPreviousMessages) {
|
|
332
|
+
const prevIds = await this.getRange(
|
|
333
|
+
threadMessagesKey,
|
|
334
|
+
Math.max(0, rank - item.withPreviousMessages),
|
|
335
|
+
rank - 1
|
|
336
|
+
);
|
|
337
|
+
prevIds.forEach((id) => messageIds.add(id));
|
|
338
|
+
}
|
|
339
|
+
if (item.withNextMessages) {
|
|
340
|
+
const nextIds = await this.getRange(threadMessagesKey, rank + 1, rank + item.withNextMessages);
|
|
341
|
+
nextIds.forEach((id) => messageIds.add(id));
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
async getRecentMessages(threadId, limit, messageIds) {
|
|
347
|
+
if (limit <= 0) return;
|
|
348
|
+
try {
|
|
349
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
350
|
+
const latestIds = await this.getLastN(threadMessagesKey, limit);
|
|
351
|
+
latestIds.forEach((id) => messageIds.add(id));
|
|
352
|
+
} catch {
|
|
353
|
+
console.log(`No message order found for thread ${threadId}, skipping latest messages`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async fetchAndParseMessages(threadId, messageIds) {
|
|
357
|
+
const messages = await Promise.all(
|
|
358
|
+
messageIds.map(async (id) => {
|
|
359
|
+
try {
|
|
360
|
+
const key = this.getMessageKey(threadId, id);
|
|
361
|
+
const data = await this.getKV(storage.TABLE_MESSAGES, key);
|
|
362
|
+
if (!data) return null;
|
|
363
|
+
return typeof data === "string" ? JSON.parse(data) : data;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
this.logger.error(`Error retrieving message ${id}:`, { error });
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
return messages.filter((msg) => msg !== null);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Queue for serializing sorted order updates.
|
|
374
|
+
* Updates the sorted order for a given key. This operation is eventually consistent.
|
|
375
|
+
*/
|
|
376
|
+
updateQueue = /* @__PURE__ */ new Map();
|
|
377
|
+
/**
|
|
378
|
+
* Updates the sorted order for a given key. This operation is eventually consistent.
|
|
379
|
+
* Note: Operations on the same orderKey are serialized using a queue to prevent
|
|
380
|
+
* concurrent updates from conflicting with each other.
|
|
381
|
+
*/
|
|
382
|
+
async updateSortedMessages(orderKey, newEntries) {
|
|
383
|
+
const currentPromise = this.updateQueue.get(orderKey) || Promise.resolve();
|
|
384
|
+
const nextPromise = currentPromise.then(async () => {
|
|
385
|
+
try {
|
|
386
|
+
const currentOrder = await this.getSortedMessages(orderKey);
|
|
387
|
+
const orderMap = new Map(currentOrder.map((entry) => [entry.id, entry]));
|
|
388
|
+
for (const entry of newEntries) {
|
|
389
|
+
orderMap.set(entry.id, entry);
|
|
390
|
+
}
|
|
391
|
+
const updatedOrder = Array.from(orderMap.values()).sort((a, b) => a.score - b.score);
|
|
392
|
+
await this.putKV({
|
|
393
|
+
tableName: storage.TABLE_MESSAGES,
|
|
394
|
+
key: orderKey,
|
|
395
|
+
value: JSON.stringify(updatedOrder)
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
this.logger.error(`Error updating sorted order for key ${orderKey}:`, { error });
|
|
399
|
+
throw error;
|
|
400
|
+
} finally {
|
|
401
|
+
if (this.updateQueue.get(orderKey) === nextPromise) {
|
|
402
|
+
this.updateQueue.delete(orderKey);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
this.updateQueue.set(orderKey, nextPromise);
|
|
407
|
+
return nextPromise;
|
|
408
|
+
}
|
|
409
|
+
async getRank(orderKey, id) {
|
|
410
|
+
const order = await this.getSortedMessages(orderKey);
|
|
411
|
+
const index = order.findIndex((item) => item.id === id);
|
|
412
|
+
return index >= 0 ? index : null;
|
|
413
|
+
}
|
|
414
|
+
async getRange(orderKey, start, end) {
|
|
415
|
+
const order = await this.getSortedMessages(orderKey);
|
|
416
|
+
const actualStart = start < 0 ? Math.max(0, order.length + start) : start;
|
|
417
|
+
const actualEnd = end < 0 ? order.length + end : Math.min(end, order.length - 1);
|
|
418
|
+
const sliced = order.slice(actualStart, actualEnd + 1);
|
|
419
|
+
return sliced.map((item) => item.id);
|
|
420
|
+
}
|
|
421
|
+
async getLastN(orderKey, n) {
|
|
422
|
+
return this.getRange(orderKey, -n, -1);
|
|
423
|
+
}
|
|
424
|
+
async getFullOrder(orderKey) {
|
|
425
|
+
return this.getRange(orderKey, 0, -1);
|
|
426
|
+
}
|
|
427
|
+
getKey(tableName, record) {
|
|
428
|
+
const prefix = this.namespacePrefix ? `${this.namespacePrefix}:` : "";
|
|
429
|
+
switch (tableName) {
|
|
430
|
+
case storage.TABLE_THREADS:
|
|
431
|
+
if (!record.id) throw new Error("Thread ID is required");
|
|
432
|
+
return `${prefix}${tableName}:${record.id}`;
|
|
433
|
+
case storage.TABLE_MESSAGES:
|
|
434
|
+
if (!record.threadId || !record.id) throw new Error("Thread ID and Message ID are required");
|
|
435
|
+
return `${prefix}${tableName}:${record.threadId}:${record.id}`;
|
|
436
|
+
case storage.TABLE_WORKFLOW_SNAPSHOT:
|
|
437
|
+
if (!record.namespace || !record.workflow_name || !record.run_id) {
|
|
438
|
+
throw new Error("Namespace, workflow name, and run ID are required");
|
|
439
|
+
}
|
|
440
|
+
return `${prefix}${tableName}:${record.namespace}:${record.workflow_name}:${record.run_id}`;
|
|
441
|
+
case storage.TABLE_TRACES:
|
|
442
|
+
if (!record.id) throw new Error("Trace ID is required");
|
|
443
|
+
return `${prefix}${tableName}:${record.id}`;
|
|
444
|
+
default:
|
|
445
|
+
throw new Error(`Unsupported table: ${tableName}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
getSchemaKey(tableName) {
|
|
449
|
+
const prefix = this.namespacePrefix ? `${this.namespacePrefix}:` : "";
|
|
450
|
+
return `${prefix}schema:${tableName}`;
|
|
451
|
+
}
|
|
452
|
+
async getTableSchema(tableName) {
|
|
453
|
+
try {
|
|
454
|
+
const schemaKey = this.getSchemaKey(tableName);
|
|
455
|
+
return await this.getKV(tableName, schemaKey);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
this.logger.error(`Failed to get schema for ${tableName}:`, { error });
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
validateColumnValue(value, column) {
|
|
462
|
+
if (value === void 0 || value === null) {
|
|
463
|
+
return column.nullable ?? false;
|
|
464
|
+
}
|
|
465
|
+
switch (column.type) {
|
|
466
|
+
case "text":
|
|
467
|
+
case "uuid":
|
|
468
|
+
return typeof value === "string";
|
|
469
|
+
case "integer":
|
|
470
|
+
case "bigint":
|
|
471
|
+
return typeof value === "number";
|
|
472
|
+
case "timestamp":
|
|
473
|
+
return value instanceof Date || typeof value === "string" && !isNaN(Date.parse(value));
|
|
474
|
+
case "jsonb":
|
|
475
|
+
if (typeof value !== "object") return false;
|
|
476
|
+
try {
|
|
477
|
+
JSON.stringify(value);
|
|
478
|
+
return true;
|
|
479
|
+
} catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
default:
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async validateAgainstSchema(record, schema) {
|
|
487
|
+
try {
|
|
488
|
+
if (!schema || typeof schema !== "object" || schema.value === null) {
|
|
489
|
+
throw new Error("Invalid schema format");
|
|
490
|
+
}
|
|
491
|
+
for (const [columnName, column] of Object.entries(schema)) {
|
|
492
|
+
const value = record[columnName];
|
|
493
|
+
if (column.primaryKey && (value === void 0 || value === null)) {
|
|
494
|
+
throw new Error(`Missing primary key value for column ${columnName}`);
|
|
495
|
+
}
|
|
496
|
+
if (!this.validateColumnValue(value, column)) {
|
|
497
|
+
const valueType = value === null ? "null" : typeof value;
|
|
498
|
+
throw new Error(`Invalid value for column ${columnName}: expected ${column.type}, got ${valueType}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
this.logger.error(`Error validating record against schema:`, { error, record, schema });
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async validateRecord(record, tableName) {
|
|
507
|
+
try {
|
|
508
|
+
if (!record || typeof record !== "object") {
|
|
509
|
+
throw new Error("Record must be an object");
|
|
510
|
+
}
|
|
511
|
+
const recordTyped = record;
|
|
512
|
+
const schema = await this.getTableSchema(tableName);
|
|
513
|
+
if (schema) {
|
|
514
|
+
await this.validateAgainstSchema(recordTyped, schema);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
switch (tableName) {
|
|
518
|
+
case storage.TABLE_THREADS:
|
|
519
|
+
if (!("id" in recordTyped) || !("resourceId" in recordTyped) || !("title" in recordTyped)) {
|
|
520
|
+
throw new Error("Thread record missing required fields");
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
case storage.TABLE_MESSAGES:
|
|
524
|
+
if (!("id" in recordTyped) || !("threadId" in recordTyped) || !("content" in recordTyped) || !("role" in recordTyped)) {
|
|
525
|
+
throw new Error("Message record missing required fields");
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
case storage.TABLE_WORKFLOW_SNAPSHOT:
|
|
529
|
+
if (!("namespace" in recordTyped) || !("workflowName" in recordTyped) || !("runId" in recordTyped)) {
|
|
530
|
+
throw new Error("Workflow record missing required fields");
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
case storage.TABLE_TRACES:
|
|
534
|
+
if (!("id" in recordTyped)) {
|
|
535
|
+
throw new Error("Trace record missing required fields");
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
default:
|
|
539
|
+
throw new Error(`Unknown table type: ${tableName}`);
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
this.logger.error(`Failed to validate record for ${tableName}:`, { error, record });
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
ensureDate(date) {
|
|
547
|
+
if (!date) return void 0;
|
|
548
|
+
return date instanceof Date ? date : new Date(date);
|
|
549
|
+
}
|
|
550
|
+
serializeDate(date) {
|
|
551
|
+
if (!date) return void 0;
|
|
552
|
+
const dateObj = this.ensureDate(date);
|
|
553
|
+
return dateObj?.toISOString();
|
|
554
|
+
}
|
|
555
|
+
ensureMetadata(metadata) {
|
|
556
|
+
if (!metadata) return {};
|
|
557
|
+
return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
|
|
558
|
+
}
|
|
559
|
+
async createTable({
|
|
560
|
+
tableName,
|
|
561
|
+
schema
|
|
562
|
+
}) {
|
|
563
|
+
try {
|
|
564
|
+
const schemaKey = this.getSchemaKey(tableName);
|
|
565
|
+
const metadata = {
|
|
566
|
+
type: "table_schema",
|
|
567
|
+
tableName,
|
|
568
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
569
|
+
};
|
|
570
|
+
await this.putKV({ tableName, key: schemaKey, value: schema, metadata });
|
|
571
|
+
} catch (error) {
|
|
572
|
+
this.logger.error(`Failed to store schema for ${tableName}:`, error);
|
|
573
|
+
throw new Error(`Failed to store schema: ${error.message}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async clearTable({ tableName }) {
|
|
577
|
+
const keys = await this.listKV(tableName);
|
|
578
|
+
if (keys.length > 0) {
|
|
579
|
+
await Promise.all(keys.map((keyObj) => this.deleteKV(tableName, keyObj.name)));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async insert({ tableName, record }) {
|
|
583
|
+
try {
|
|
584
|
+
const key = this.getKey(tableName, record);
|
|
585
|
+
const processedRecord = {
|
|
586
|
+
...record,
|
|
587
|
+
createdAt: record.createdAt ? this.serializeDate(record.createdAt) : void 0,
|
|
588
|
+
updatedAt: record.updatedAt ? this.serializeDate(record.updatedAt) : void 0,
|
|
589
|
+
metadata: record.metadata ? JSON.stringify(record.metadata) : ""
|
|
590
|
+
};
|
|
591
|
+
await this.validateRecord(processedRecord, tableName);
|
|
592
|
+
await this.putKV({ tableName, key, value: processedRecord });
|
|
593
|
+
} catch (error) {
|
|
594
|
+
this.logger.error(`Failed to insert record for ${tableName}:`, { error });
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
async load({ tableName, keys }) {
|
|
599
|
+
try {
|
|
600
|
+
const key = this.getKey(tableName, keys);
|
|
601
|
+
const data = await this.getKV(tableName, key);
|
|
602
|
+
if (!data) return null;
|
|
603
|
+
const processed = {
|
|
604
|
+
...data,
|
|
605
|
+
createdAt: this.ensureDate(data.createdAt),
|
|
606
|
+
updatedAt: this.ensureDate(data.updatedAt),
|
|
607
|
+
metadata: this.ensureMetadata(data.metadata)
|
|
608
|
+
};
|
|
609
|
+
return processed;
|
|
610
|
+
} catch (error) {
|
|
611
|
+
this.logger.error(`Failed to load data for ${tableName}:`, { error });
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async getThreadById({ threadId }) {
|
|
616
|
+
const thread = await this.load({ tableName: storage.TABLE_THREADS, keys: { id: threadId } });
|
|
617
|
+
if (!thread) return null;
|
|
618
|
+
try {
|
|
619
|
+
return {
|
|
620
|
+
...thread,
|
|
621
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
622
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
623
|
+
metadata: this.ensureMetadata(thread.metadata)
|
|
624
|
+
};
|
|
625
|
+
} catch (error) {
|
|
626
|
+
this.logger.error(`Error processing thread ${threadId}:`, { error });
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async getThreadsByResourceId({ resourceId }) {
|
|
631
|
+
try {
|
|
632
|
+
const keyList = await this.listKV(storage.TABLE_THREADS);
|
|
633
|
+
const threads = await Promise.all(
|
|
634
|
+
keyList.map(async (keyObj) => {
|
|
635
|
+
try {
|
|
636
|
+
const data = await this.getKV(storage.TABLE_THREADS, keyObj.name);
|
|
637
|
+
if (!data) return null;
|
|
638
|
+
const thread = typeof data === "string" ? JSON.parse(data) : data;
|
|
639
|
+
if (!thread || !thread.resourceId || thread.resourceId !== resourceId) return null;
|
|
640
|
+
return {
|
|
641
|
+
...thread,
|
|
642
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
643
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
644
|
+
metadata: this.ensureMetadata(thread.metadata)
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
this.logger.error(`Error processing thread from key ${keyObj.name}:`, { error });
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
})
|
|
651
|
+
);
|
|
652
|
+
return threads.filter((thread) => thread !== null);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
this.logger.error(`Error getting threads for resourceId ${resourceId}:`, { error });
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async saveThread({ thread }) {
|
|
659
|
+
try {
|
|
660
|
+
await this.insert({ tableName: storage.TABLE_THREADS, record: thread });
|
|
661
|
+
return thread;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
this.logger.error("Error saving thread:", { error });
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async updateThread({
|
|
668
|
+
id,
|
|
669
|
+
title,
|
|
670
|
+
metadata
|
|
671
|
+
}) {
|
|
672
|
+
try {
|
|
673
|
+
const thread = await this.getThreadById({ threadId: id });
|
|
674
|
+
if (!thread) {
|
|
675
|
+
throw new Error(`Thread ${id} not found`);
|
|
676
|
+
}
|
|
677
|
+
const updatedThread = {
|
|
678
|
+
...thread,
|
|
679
|
+
title,
|
|
680
|
+
metadata: this.ensureMetadata({
|
|
681
|
+
...thread.metadata ?? {},
|
|
682
|
+
...metadata
|
|
683
|
+
}),
|
|
684
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
685
|
+
};
|
|
686
|
+
await this.insert({ tableName: storage.TABLE_THREADS, record: updatedThread });
|
|
687
|
+
return updatedThread;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.logger.error(`Error updating thread ${id}:`, { error });
|
|
690
|
+
throw error;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async deleteThread({ threadId }) {
|
|
694
|
+
try {
|
|
695
|
+
const thread = await this.getThreadById({ threadId });
|
|
696
|
+
if (!thread) {
|
|
697
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
698
|
+
}
|
|
699
|
+
const messageKeys = await this.listKV(storage.TABLE_MESSAGES);
|
|
700
|
+
const threadMessageKeys = messageKeys.filter((key) => key.name.includes(`${storage.TABLE_MESSAGES}:${threadId}:`));
|
|
701
|
+
await Promise.all([
|
|
702
|
+
// Delete message order
|
|
703
|
+
this.deleteKV(storage.TABLE_MESSAGES, this.getThreadMessagesKey(threadId)),
|
|
704
|
+
// Delete all messages
|
|
705
|
+
...threadMessageKeys.map((key) => this.deleteKV(storage.TABLE_MESSAGES, key.name)),
|
|
706
|
+
// Delete thread
|
|
707
|
+
this.deleteKV(storage.TABLE_THREADS, this.getKey(storage.TABLE_THREADS, { id: threadId }))
|
|
708
|
+
]);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
this.logger.error(`Error deleting thread ${threadId}:`, { error });
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
getMessageKey(threadId, messageId) {
|
|
715
|
+
try {
|
|
716
|
+
return this.getKey(storage.TABLE_MESSAGES, { threadId, id: messageId });
|
|
717
|
+
} catch (error) {
|
|
718
|
+
this.logger.error(`Error getting message key for thread ${threadId} and message ${messageId}:`, { error });
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
getThreadMessagesKey(threadId) {
|
|
723
|
+
try {
|
|
724
|
+
return this.getKey(storage.TABLE_MESSAGES, { threadId, id: "messages" });
|
|
725
|
+
} catch (error) {
|
|
726
|
+
this.logger.error(`Error getting thread messages key for thread ${threadId}:`, { error });
|
|
727
|
+
throw error;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async saveMessages({ messages }) {
|
|
731
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
732
|
+
try {
|
|
733
|
+
const validatedMessages = messages.map((message, index) => {
|
|
734
|
+
const errors = [];
|
|
735
|
+
if (!message.id) errors.push("id is required");
|
|
736
|
+
if (!message.threadId) errors.push("threadId is required");
|
|
737
|
+
if (!message.content) errors.push("content is required");
|
|
738
|
+
if (!message.role) errors.push("role is required");
|
|
739
|
+
if (!message.createdAt) errors.push("createdAt is required");
|
|
740
|
+
if (errors.length > 0) {
|
|
741
|
+
throw new Error(`Invalid message at index ${index}: ${errors.join(", ")}`);
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
...message,
|
|
745
|
+
createdAt: this.ensureDate(message.createdAt),
|
|
746
|
+
type: message.type || "text",
|
|
747
|
+
_index: index
|
|
748
|
+
};
|
|
749
|
+
});
|
|
750
|
+
const messagesByThread = validatedMessages.reduce((acc, message) => {
|
|
751
|
+
if (!acc.has(message.threadId)) {
|
|
752
|
+
acc.set(message.threadId, []);
|
|
753
|
+
}
|
|
754
|
+
acc.get(message.threadId).push(message);
|
|
755
|
+
return acc;
|
|
756
|
+
}, /* @__PURE__ */ new Map());
|
|
757
|
+
await Promise.all(
|
|
758
|
+
Array.from(messagesByThread.entries()).map(async ([threadId, threadMessages]) => {
|
|
759
|
+
try {
|
|
760
|
+
const thread = await this.getThreadById({ threadId });
|
|
761
|
+
if (!thread) {
|
|
762
|
+
throw new Error(`Thread ${threadId} not found`);
|
|
763
|
+
}
|
|
764
|
+
await Promise.all(
|
|
765
|
+
threadMessages.map(async (message) => {
|
|
766
|
+
const key = await this.getMessageKey(threadId, message.id);
|
|
767
|
+
const { _index, ...cleanMessage } = message;
|
|
768
|
+
const serializedMessage = {
|
|
769
|
+
...cleanMessage,
|
|
770
|
+
createdAt: this.serializeDate(cleanMessage.createdAt)
|
|
771
|
+
};
|
|
772
|
+
await this.putKV({ tableName: storage.TABLE_MESSAGES, key, value: serializedMessage });
|
|
773
|
+
})
|
|
774
|
+
);
|
|
775
|
+
const orderKey = this.getThreadMessagesKey(threadId);
|
|
776
|
+
const entries = await this.updateSorting(threadMessages);
|
|
777
|
+
await this.updateSortedMessages(orderKey, entries);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
780
|
+
this.logger.error(`Error processing messages for thread ${threadId}: ${errorMessage}`);
|
|
781
|
+
throw error;
|
|
782
|
+
}
|
|
783
|
+
})
|
|
784
|
+
);
|
|
785
|
+
return validatedMessages.map(({ _index, ...message }) => message);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
788
|
+
this.logger.error(`Error saving messages: ${errorMessage}`);
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async getMessages({ threadId, selectBy }) {
|
|
793
|
+
if (!threadId) throw new Error("threadId is required");
|
|
794
|
+
let limit = 40;
|
|
795
|
+
if (typeof selectBy?.last === "number") {
|
|
796
|
+
limit = Math.max(0, selectBy.last);
|
|
797
|
+
} else if (selectBy?.last === false) {
|
|
798
|
+
limit = 0;
|
|
799
|
+
}
|
|
800
|
+
const messageIds = /* @__PURE__ */ new Set();
|
|
801
|
+
if (limit === 0 && !selectBy?.include?.length) return [];
|
|
802
|
+
try {
|
|
803
|
+
await Promise.all([
|
|
804
|
+
selectBy?.include?.length ? this.getIncludedMessagesWithContext(threadId, selectBy.include, messageIds) : Promise.resolve(),
|
|
805
|
+
limit > 0 && !selectBy?.include?.length ? this.getRecentMessages(threadId, limit, messageIds) : Promise.resolve()
|
|
806
|
+
]);
|
|
807
|
+
const messages = await this.fetchAndParseMessages(threadId, Array.from(messageIds));
|
|
808
|
+
if (!messages.length) return [];
|
|
809
|
+
try {
|
|
810
|
+
const threadMessagesKey = this.getThreadMessagesKey(threadId);
|
|
811
|
+
const messageOrder = await this.getFullOrder(threadMessagesKey);
|
|
812
|
+
const orderMap = new Map(messageOrder.map((id, index) => [id, index]));
|
|
813
|
+
messages.sort((a, b) => {
|
|
814
|
+
const indexA = orderMap.get(a.id);
|
|
815
|
+
const indexB = orderMap.get(b.id);
|
|
816
|
+
if (indexA !== void 0 && indexB !== void 0) return orderMap.get(a.id) - orderMap.get(b.id);
|
|
817
|
+
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
818
|
+
});
|
|
819
|
+
} catch (error) {
|
|
820
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
821
|
+
this.logger.warn(`Error sorting messages, falling back to creation time: ${errorMessage}`);
|
|
822
|
+
messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
823
|
+
}
|
|
824
|
+
return messages.map(({ _index, ...message }) => ({
|
|
825
|
+
...message,
|
|
826
|
+
createdAt: this.ensureDate(message.createdAt)
|
|
827
|
+
}));
|
|
828
|
+
} catch (error) {
|
|
829
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
830
|
+
this.logger.error(`Error retrieving messages for thread ${threadId}: ${errorMessage}`);
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
validateWorkflowParams(params) {
|
|
835
|
+
const { namespace, workflowName, runId } = params;
|
|
836
|
+
if (!namespace || !workflowName || !runId) {
|
|
837
|
+
throw new Error("Invalid workflow snapshot parameters");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
validateWorkflowState(state) {
|
|
841
|
+
if (!state?.runId || !state?.value || !state?.context?.steps || !state?.context?.triggerData || !state?.context?.attempts || !state?.activePaths) {
|
|
842
|
+
throw new Error("Invalid workflow state structure");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
normalizeSteps(steps) {
|
|
846
|
+
const normalizedSteps = {};
|
|
847
|
+
for (const [stepId, step] of Object.entries(steps)) {
|
|
848
|
+
normalizedSteps[stepId] = {
|
|
849
|
+
status: step.status,
|
|
850
|
+
payload: step.payload || step.result,
|
|
851
|
+
error: step.error
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return normalizedSteps;
|
|
855
|
+
}
|
|
856
|
+
normalizeWorkflowState(data) {
|
|
857
|
+
const steps = data.context?.stepResults || data.context?.steps || {};
|
|
858
|
+
return {
|
|
859
|
+
runId: data.runId,
|
|
860
|
+
value: data.value,
|
|
861
|
+
context: {
|
|
862
|
+
steps: this.normalizeSteps(steps),
|
|
863
|
+
triggerData: data.context?.triggerData || {},
|
|
864
|
+
attempts: data.context?.attempts || {}
|
|
865
|
+
},
|
|
866
|
+
activePaths: data.activePaths || [],
|
|
867
|
+
timestamp: data.timestamp || Date.now()
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
async persistWorkflowSnapshot(params) {
|
|
871
|
+
try {
|
|
872
|
+
this.validateWorkflowParams(params);
|
|
873
|
+
const { namespace, workflowName, runId, snapshot } = params;
|
|
874
|
+
const normalizedState = this.normalizeWorkflowState(snapshot);
|
|
875
|
+
this.validateWorkflowState(normalizedState);
|
|
876
|
+
const key = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId });
|
|
877
|
+
await this.putKV({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, key, value: normalizedState });
|
|
878
|
+
} catch (error) {
|
|
879
|
+
this.logger.error("Error persisting workflow snapshot:", { error });
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async loadWorkflowSnapshot(params) {
|
|
884
|
+
try {
|
|
885
|
+
this.validateWorkflowParams(params);
|
|
886
|
+
const { namespace, workflowName, runId } = params;
|
|
887
|
+
const key = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId });
|
|
888
|
+
const data = await this.getKV(storage.TABLE_WORKFLOW_SNAPSHOT, key);
|
|
889
|
+
if (!data) return null;
|
|
890
|
+
const state = this.normalizeWorkflowState(data);
|
|
891
|
+
this.validateWorkflowState(state);
|
|
892
|
+
return state;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
this.logger.error("Error loading workflow snapshot:", { error });
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async batchInsert(input) {
|
|
899
|
+
if (!input.records || input.records.length === 0) return;
|
|
900
|
+
try {
|
|
901
|
+
await Promise.all(
|
|
902
|
+
input.records.map(async (record) => {
|
|
903
|
+
const key = this.getKey(input.tableName, record);
|
|
904
|
+
const processedRecord = {
|
|
905
|
+
...record,
|
|
906
|
+
createdAt: record.createdAt ? this.serializeDate(record.createdAt) : void 0,
|
|
907
|
+
updatedAt: record.updatedAt ? this.serializeDate(record.updatedAt) : void 0,
|
|
908
|
+
metadata: record.metadata ? JSON.stringify(record.metadata) : void 0
|
|
909
|
+
};
|
|
910
|
+
await this.putKV({ tableName: input.tableName, key, value: processedRecord });
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
this.logger.error("Error in batch insert:", { error });
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async getTraces({
|
|
919
|
+
name,
|
|
920
|
+
scope,
|
|
921
|
+
page = 0,
|
|
922
|
+
perPage = 100,
|
|
923
|
+
attributes
|
|
924
|
+
}) {
|
|
925
|
+
try {
|
|
926
|
+
let keys;
|
|
927
|
+
if (this.bindings) {
|
|
928
|
+
keys = (await this.listKV(storage.TABLE_TRACES))?.map((k) => k.name) || [];
|
|
929
|
+
} else {
|
|
930
|
+
const namespaceId = await this.getNamespaceId(storage.TABLE_TRACES);
|
|
931
|
+
const result = await this.client.kv.namespaces.keys.list(namespaceId, {
|
|
932
|
+
prefix: "",
|
|
933
|
+
limit: 1e3,
|
|
934
|
+
account_id: this.accountId
|
|
935
|
+
});
|
|
936
|
+
keys = result.result?.map((k) => k.name) || [];
|
|
937
|
+
}
|
|
938
|
+
const traceRecords = await Promise.all(
|
|
939
|
+
keys.map(async (key) => {
|
|
940
|
+
const record = await this.getKV(storage.TABLE_TRACES, key);
|
|
941
|
+
if (!record) return null;
|
|
942
|
+
return record;
|
|
943
|
+
})
|
|
944
|
+
);
|
|
945
|
+
let filteredTraces = traceRecords.filter(
|
|
946
|
+
(record) => record !== null && typeof record === "object"
|
|
947
|
+
);
|
|
948
|
+
if (name) {
|
|
949
|
+
filteredTraces = filteredTraces.filter((record) => record.name?.toLowerCase().startsWith(name.toLowerCase()));
|
|
950
|
+
}
|
|
951
|
+
if (scope) {
|
|
952
|
+
filteredTraces = filteredTraces.filter((record) => record.scope === scope);
|
|
953
|
+
}
|
|
954
|
+
if (attributes) {
|
|
955
|
+
filteredTraces = filteredTraces.filter((record) => {
|
|
956
|
+
if (!record.attributes) return false;
|
|
957
|
+
const recordAttrs = this.parseJSON(record.attributes);
|
|
958
|
+
if (!recordAttrs) return false;
|
|
959
|
+
return Object.entries(attributes).every(([key, value]) => recordAttrs[key] === value);
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
filteredTraces.sort((a, b) => {
|
|
963
|
+
const dateA = new Date(a.createdAt).getTime();
|
|
964
|
+
const dateB = new Date(b.createdAt).getTime();
|
|
965
|
+
return dateB - dateA;
|
|
966
|
+
});
|
|
967
|
+
const start = page * perPage;
|
|
968
|
+
const end = start + perPage;
|
|
969
|
+
const paginatedTraces = filteredTraces.slice(start, end);
|
|
970
|
+
return paginatedTraces.map((record) => ({
|
|
971
|
+
id: record.id,
|
|
972
|
+
parentSpanId: record.parentSpanId,
|
|
973
|
+
traceId: record.traceId,
|
|
974
|
+
name: record.name,
|
|
975
|
+
scope: record.scope,
|
|
976
|
+
kind: record.kind,
|
|
977
|
+
status: this.parseJSON(record.status),
|
|
978
|
+
events: this.parseJSON(record.events) || [],
|
|
979
|
+
links: this.parseJSON(record.links) || [],
|
|
980
|
+
attributes: this.parseJSON(record?.attributes) || {},
|
|
981
|
+
startTime: record.startTime,
|
|
982
|
+
endTime: record.endTime,
|
|
983
|
+
other: this.parseJSON(record.other) || {},
|
|
984
|
+
createdAt: record.createdAt
|
|
985
|
+
}));
|
|
986
|
+
} catch (error) {
|
|
987
|
+
this.logger.error("Failed to get traces:", { message: error instanceof Error ? error.message : String(error) });
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
parseJSON(value) {
|
|
992
|
+
if (typeof value === "string") {
|
|
993
|
+
try {
|
|
994
|
+
return JSON.parse(value);
|
|
995
|
+
} catch {
|
|
996
|
+
return value;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return value;
|
|
1000
|
+
}
|
|
1001
|
+
getEvalsByAgentName(_agentName, _type) {
|
|
1002
|
+
throw new Error("Method not implemented.");
|
|
1003
|
+
}
|
|
1004
|
+
getWorkflowRuns(_args) {
|
|
1005
|
+
throw new Error("Method not implemented.");
|
|
1006
|
+
}
|
|
1007
|
+
async close() {
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
exports.CloudflareStore = CloudflareStore;
|