@mastra/convex 1.4.0 → 1.5.0

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.
@@ -1,1776 +0,0 @@
1
- 'use strict';
2
-
3
- var server = require('convex/server');
4
- var constants = require('@mastra/core/storage/constants');
5
- var values = require('convex/values');
6
-
7
- // src/server/cache.ts
8
- var CACHE_TABLE = "mastra_cache";
9
- var CACHE_LIST_TABLE = "mastra_cache_list_items";
10
- var CACHE_MUTATION_BATCH_SIZE = 25;
11
- function encodeValue(value) {
12
- return JSON.stringify(value === void 0 ? null : value);
13
- }
14
- function decodeValue(value) {
15
- return JSON.parse(value);
16
- }
17
- function isExpired(doc, now) {
18
- return doc.expiresAt !== null && doc.expiresAt <= now;
19
- }
20
- function normalizeListRange(from, to, length) {
21
- const normalizedFrom = from < 0 ? Math.max(length + from, 0) : from;
22
- const normalizedTo = to < 0 ? length + to : to;
23
- if (normalizedTo < normalizedFrom || normalizedFrom >= length) return null;
24
- return { from: normalizedFrom, to: normalizedTo };
25
- }
26
- async function findCacheDoc(ctx, key) {
27
- return await ctx.db.query(CACHE_TABLE).withIndex("by_key", (q) => q.eq("key", key)).first();
28
- }
29
- async function deleteCacheKey(ctx, key) {
30
- const [doc, listItems] = await Promise.all([
31
- findCacheDoc(ctx, key),
32
- ctx.db.query(CACHE_LIST_TABLE).withIndex("by_key_index", (q) => q.eq("key", key)).take(CACHE_MUTATION_BATCH_SIZE + 1)
33
- ]);
34
- if (doc && doc.kind !== "deleted") {
35
- await ctx.db.patch(doc._id, { kind: "deleted" });
36
- }
37
- for (const item of listItems.slice(0, CACHE_MUTATION_BATCH_SIZE)) {
38
- await ctx.db.delete(item._id);
39
- }
40
- const hasMore = listItems.length > CACHE_MUTATION_BATCH_SIZE;
41
- if (doc && !hasMore) {
42
- await ctx.db.delete(doc._id);
43
- }
44
- return {
45
- hasMore
46
- };
47
- }
48
- async function getLiveCacheDoc(ctx, key, now) {
49
- const doc = await findCacheDoc(ctx, key);
50
- if (!doc) return { doc: null, hasMore: false };
51
- if (doc.kind === "deleted") {
52
- const cleanup2 = await deleteCacheKey(ctx, key);
53
- return { doc: null, hasMore: cleanup2.hasMore };
54
- }
55
- if (!isExpired(doc, now)) return { doc, hasMore: false };
56
- const cleanup = await deleteCacheKey(ctx, key);
57
- return { doc: null, hasMore: cleanup.hasMore };
58
- }
59
- async function writeCacheDoc(ctx, key, existing, patch) {
60
- if (existing) {
61
- await ctx.db.patch(existing._id, patch);
62
- return { ...existing, ...patch };
63
- }
64
- const _id = await ctx.db.insert(CACHE_TABLE, { key, ...patch });
65
- return { _id, key, ...patch };
66
- }
67
- async function clearPrefix(ctx, keyPrefix) {
68
- const [docs, orphanListItems] = await Promise.all([
69
- ctx.db.query(CACHE_TABLE).withIndex("by_key_prefix", (q) => q.eq("keyPrefix", keyPrefix)).take(CACHE_MUTATION_BATCH_SIZE + 1),
70
- ctx.db.query(CACHE_LIST_TABLE).withIndex("by_key_prefix", (q) => q.eq("keyPrefix", keyPrefix)).take(1)
71
- ]);
72
- if (docs.length > 0) {
73
- for (const doc of docs.slice(0, CACHE_MUTATION_BATCH_SIZE)) {
74
- if (doc.kind === "list" || doc.kind === "deleted") {
75
- const cleanup = await deleteCacheKey(ctx, doc.key);
76
- return cleanup.hasMore || docs.length > 1 || orphanListItems.length > 0;
77
- }
78
- await ctx.db.delete(doc._id);
79
- }
80
- return docs.length > CACHE_MUTATION_BATCH_SIZE || orphanListItems.length > 0;
81
- }
82
- const listItems = await ctx.db.query(CACHE_LIST_TABLE).withIndex("by_key_prefix", (q) => q.eq("keyPrefix", keyPrefix)).take(CACHE_MUTATION_BATCH_SIZE + 1);
83
- for (const item of listItems.slice(0, CACHE_MUTATION_BATCH_SIZE)) {
84
- await ctx.db.delete(item._id);
85
- }
86
- return listItems.length > CACHE_MUTATION_BATCH_SIZE;
87
- }
88
- async function handleCacheOperation(ctx, request) {
89
- const now = Date.now();
90
- switch (request.op) {
91
- case "get": {
92
- const { doc, hasMore } = await getLiveCacheDoc(ctx, request.key, now);
93
- if (hasMore) return { ok: true, result: null, hasMore: true };
94
- if (!doc || doc.kind !== "value") return { ok: true, result: null };
95
- return { ok: true, result: decodeValue(doc.value ?? "null") };
96
- }
97
- case "set": {
98
- let existing = await findCacheDoc(ctx, request.key);
99
- if (existing && (isExpired(existing, now) || existing.kind !== "value")) {
100
- const cleanup = await deleteCacheKey(ctx, request.key);
101
- if (cleanup.hasMore) {
102
- return { ok: true, hasMore: true };
103
- }
104
- existing = null;
105
- }
106
- await writeCacheDoc(ctx, request.key, existing, {
107
- keyPrefix: request.keyPrefix,
108
- kind: "value",
109
- value: encodeValue(request.value),
110
- expiresAt: request.expiresAt
111
- });
112
- return { ok: true };
113
- }
114
- case "listLength": {
115
- const { doc, hasMore } = await getLiveCacheDoc(ctx, request.key, now);
116
- if (hasMore) return { ok: true, result: 0, hasMore: true };
117
- if (!doc) return { ok: true, result: 0 };
118
- if (doc.kind !== "list") return { ok: false, error: `${request.key} exists but is not an array` };
119
- return { ok: true, result: doc.counter ?? 0 };
120
- }
121
- case "listPush": {
122
- let existing = await findCacheDoc(ctx, request.key);
123
- if (existing && (isExpired(existing, now) || existing.kind === "deleted")) {
124
- const cleanup = await deleteCacheKey(ctx, request.key);
125
- if (cleanup.hasMore) {
126
- return { ok: true, hasMore: true };
127
- }
128
- existing = null;
129
- }
130
- if (existing && existing.kind !== "list") {
131
- return { ok: false, error: `${request.key} exists but is not an array` };
132
- }
133
- const doc = existing ? await writeCacheDoc(ctx, request.key, existing, {
134
- kind: "list",
135
- keyPrefix: request.keyPrefix,
136
- counter: (existing.counter ?? 0) + 1,
137
- expiresAt: request.expiresAt
138
- }) : await writeCacheDoc(ctx, request.key, null, {
139
- kind: "list",
140
- keyPrefix: request.keyPrefix,
141
- counter: 1,
142
- expiresAt: request.expiresAt
143
- });
144
- await ctx.db.insert(CACHE_LIST_TABLE, {
145
- key: request.key,
146
- keyPrefix: request.keyPrefix,
147
- index: (doc.counter ?? 1) - 1,
148
- value: encodeValue(request.value)
149
- });
150
- return { ok: true };
151
- }
152
- case "listFromTo": {
153
- const { doc, hasMore } = await getLiveCacheDoc(ctx, request.key, now);
154
- if (hasMore) return { ok: true, result: [], hasMore: true };
155
- if (!doc || doc.kind !== "list") return { ok: true, result: [] };
156
- const range = normalizeListRange(request.from, request.to, doc.counter ?? 0);
157
- if (!range) return { ok: true, result: [] };
158
- const query = ctx.db.query(CACHE_LIST_TABLE).withIndex("by_key_index", (q) => {
159
- return q.eq("key", request.key).gte("index", range.from).lte("index", range.to);
160
- });
161
- const items = await query.collect();
162
- return { ok: true, result: items.map((item) => decodeValue(item.value)) };
163
- }
164
- case "delete": {
165
- const cleanup = await deleteCacheKey(ctx, request.key);
166
- return { ok: true, hasMore: cleanup.hasMore };
167
- }
168
- case "clear": {
169
- const hasMore = await clearPrefix(ctx, request.keyPrefix);
170
- return { ok: true, hasMore };
171
- }
172
- case "increment": {
173
- let existing = await findCacheDoc(ctx, request.key);
174
- if (existing && (isExpired(existing, now) || existing.kind === "deleted")) {
175
- const cleanup = await deleteCacheKey(ctx, request.key);
176
- if (cleanup.hasMore) {
177
- return { ok: true, hasMore: true };
178
- }
179
- existing = null;
180
- }
181
- if (existing && existing.kind !== "counter") {
182
- return { ok: false, error: `${request.key} exists but is not a number` };
183
- }
184
- const nextCounter = (existing?.counter ?? 0) + 1;
185
- await writeCacheDoc(ctx, request.key, existing, {
186
- kind: "counter",
187
- keyPrefix: request.keyPrefix,
188
- counter: nextCounter,
189
- expiresAt: request.expiresAt
190
- });
191
- return { ok: true, result: nextCounter };
192
- }
193
- }
194
- return { ok: false, error: `Unsupported operation ${request.op}` };
195
- }
196
- var mastraCache = server.mutationGeneric(
197
- async (ctx, request) => handleCacheOperation(ctx, request)
198
- );
199
-
200
- // src/server/index-map.ts
201
- var TABLE_INDEX_MAP = {
202
- mastra_messages: [
203
- { name: "by_thread_created", fields: ["thread_id", "createdAt"] },
204
- { name: "by_thread", fields: ["thread_id"] },
205
- { name: "by_resource", fields: ["resourceId"] },
206
- { name: "by_record_id", fields: ["id"] }
207
- ],
208
- mastra_threads: [
209
- { name: "by_resource", fields: ["resourceId"] },
210
- { name: "by_created", fields: ["createdAt"] },
211
- { name: "by_updated", fields: ["updatedAt"] },
212
- { name: "by_record_id", fields: ["id"] }
213
- ],
214
- mastra_resources: [
215
- { name: "by_updated", fields: ["updatedAt"] },
216
- { name: "by_record_id", fields: ["id"] }
217
- ],
218
- mastra_workflow_snapshots: [
219
- { name: "by_workflow_run", fields: ["workflow_name", "run_id"] },
220
- { name: "by_workflow", fields: ["workflow_name"] },
221
- { name: "by_resource", fields: ["resourceId"] },
222
- { name: "by_created", fields: ["createdAt"] },
223
- { name: "by_record_id", fields: ["id"] }
224
- ],
225
- mastra_scorers: [
226
- { name: "by_entity", fields: ["entityId", "entityType"] },
227
- { name: "by_scorer", fields: ["scorerId"] },
228
- { name: "by_run", fields: ["runId"] },
229
- { name: "by_created", fields: ["createdAt"] },
230
- { name: "by_record_id", fields: ["id"] }
231
- ],
232
- mastra_schedules: [
233
- { name: "by_workflow_status", fields: ["workflow_id", "status"] },
234
- { name: "by_workflow_id", fields: ["workflow_id"] },
235
- { name: "by_owner", fields: ["owner_type", "owner_id"] },
236
- { name: "by_owner_id", fields: ["owner_id"] },
237
- { name: "by_status_next_fire_at", fields: ["status", "next_fire_at"] },
238
- { name: "by_created", fields: ["created_at"] },
239
- { name: "by_record_id", fields: ["id"] }
240
- ],
241
- mastra_schedule_triggers: [
242
- { name: "by_schedule_actual", fields: ["schedule_id", "actual_fire_at"] },
243
- { name: "by_parent_trigger", fields: ["parent_trigger_id"] },
244
- { name: "by_record_id", fields: ["id"] }
245
- ],
246
- mastra_channel_installations: [
247
- { name: "by_platform_agent", fields: ["platform", "agentId"] },
248
- { name: "by_webhook", fields: ["webhookId"] },
249
- { name: "by_platform", fields: ["platform"] },
250
- { name: "by_record_id", fields: ["id"] }
251
- ],
252
- mastra_channel_config: [
253
- { name: "by_platform", fields: ["platform"] },
254
- { name: "by_record_id", fields: ["id"] }
255
- ],
256
- mastra_background_tasks: [
257
- { name: "by_agent_status", fields: ["agent_id", "status"] },
258
- { name: "by_status_created", fields: ["status", "createdAt"] },
259
- { name: "by_run", fields: ["run_id"] },
260
- { name: "by_tool_call", fields: ["tool_call_id"] },
261
- { name: "by_thread", fields: ["thread_id"] },
262
- { name: "by_resource", fields: ["resource_id"] },
263
- { name: "by_tool", fields: ["tool_name"] },
264
- { name: "by_created", fields: ["createdAt"] },
265
- { name: "by_record_id", fields: ["id"] }
266
- ],
267
- mastra_vector_indexes: [
268
- { name: "by_name", fields: ["indexName"] },
269
- { name: "by_record_id", fields: ["id"] }
270
- ],
271
- mastra_observational_memory: [
272
- { name: "by_lookup_key", fields: ["lookupKey", "generationCount"] },
273
- { name: "by_record_id", fields: ["id"] }
274
- ]
275
- };
276
- function findBestIndex(convexTable, filters) {
277
- const indexes = TABLE_INDEX_MAP[convexTable];
278
- if (!indexes || filters.length === 0) return null;
279
- const filtersByField = /* @__PURE__ */ new Map();
280
- for (const f of filters) {
281
- filtersByField.set(f.field, f);
282
- }
283
- let best = null;
284
- for (const index of indexes) {
285
- let prefixLength = 0;
286
- const indexedFilters = [];
287
- for (const field of index.fields) {
288
- const filter = filtersByField.get(field);
289
- if (filter) {
290
- prefixLength++;
291
- indexedFilters.push(filter);
292
- } else {
293
- break;
294
- }
295
- }
296
- if (prefixLength > 0 && (!best || prefixLength > best.prefixLength)) {
297
- best = { indexName: index.name, indexedFilters, prefixLength };
298
- }
299
- }
300
- return best ? { indexName: best.indexName, indexedFilters: best.indexedFilters } : null;
301
- }
302
-
303
- // src/server/observational-memory.ts
304
- var OM_QUERY_MAX_DOCS = 1e4;
305
- function parseStoredChunks(value) {
306
- if (typeof value !== "string" || !value) return [];
307
- try {
308
- const parsed = JSON.parse(value);
309
- return Array.isArray(parsed) ? parsed : [];
310
- } catch {
311
- return [];
312
- }
313
- }
314
- function selectActivationBoundary(chunks, opts) {
315
- const retentionFloor = opts.messageTokensThreshold * (1 - opts.activationRatio);
316
- const targetMessageTokens = Math.max(0, opts.currentPendingTokens - retentionFloor);
317
- let cumulativeMessageTokens = 0;
318
- let bestOverBoundary = 0;
319
- let bestOverTokens = 0;
320
- let bestUnderBoundary = 0;
321
- let bestUnderTokens = 0;
322
- for (let i = 0; i < chunks.length; i++) {
323
- cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
324
- const boundary = i + 1;
325
- if (cumulativeMessageTokens >= targetMessageTokens) {
326
- if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
327
- bestOverBoundary = boundary;
328
- bestOverTokens = cumulativeMessageTokens;
329
- }
330
- } else {
331
- if (cumulativeMessageTokens > bestUnderTokens) {
332
- bestUnderBoundary = boundary;
333
- bestUnderTokens = cumulativeMessageTokens;
334
- }
335
- }
336
- }
337
- const maxOvershoot = retentionFloor * 0.95;
338
- const overshoot = bestOverTokens - targetMessageTokens;
339
- const remainingAfterOver = opts.currentPendingTokens - bestOverTokens;
340
- const remainingAfterUnder = opts.currentPendingTokens - bestUnderTokens;
341
- const minRemaining = Math.min(1e3, retentionFloor);
342
- if (opts.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) {
343
- return bestOverBoundary;
344
- }
345
- if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) {
346
- return bestOverBoundary;
347
- }
348
- if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) {
349
- return bestUnderBoundary;
350
- }
351
- if (bestOverBoundary > 0) {
352
- return bestOverBoundary;
353
- }
354
- return 1;
355
- }
356
- function mergeReflectionWithUnreflected(activeObservations, bufferedReflection, reflectedLineCount) {
357
- const allLines = (activeObservations || "").split("\n");
358
- const unreflectedLines = allLines.slice(reflectedLineCount);
359
- const unreflectedContent = unreflectedLines.join("\n").trim();
360
- return unreflectedContent ? `${bufferedReflection}
361
-
362
- ${unreflectedContent}` : bufferedReflection;
363
- }
364
- function isPlainObj(value) {
365
- return typeof value === "object" && value !== null && !Array.isArray(value);
366
- }
367
- function deepMergeOMConfig(target, source) {
368
- const output = { ...target };
369
- for (const key of Object.keys(source)) {
370
- const tVal = target[key];
371
- const sVal = source[key];
372
- if (isPlainObj(tVal) && isPlainObj(sVal)) {
373
- output[key] = deepMergeOMConfig(tVal, sVal);
374
- } else if (sVal !== void 0) {
375
- output[key] = sVal;
376
- }
377
- }
378
- return output;
379
- }
380
- function parseJsonObject(value) {
381
- if (typeof value !== "string" || !value) return {};
382
- try {
383
- const parsed = JSON.parse(value);
384
- return isPlainObj(parsed) ? parsed : {};
385
- } catch {
386
- return {};
387
- }
388
- }
389
- async function findRecordById(ctx, convexTable, id) {
390
- return await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
391
- }
392
- function requireRecord(doc, id) {
393
- if (!doc) {
394
- throw new Error(`Observational memory record not found: ${id}`);
395
- }
396
- return doc;
397
- }
398
- var EMPTY_SWAP_RESULT = {
399
- chunksActivated: 0,
400
- messageTokensActivated: 0,
401
- observationTokensActivated: 0,
402
- messagesActivated: 0,
403
- activatedCycleIds: [],
404
- activatedMessageIds: []
405
- };
406
- async function handleObservationalMemoryOperation(ctx, convexTable, request) {
407
- switch (request.op) {
408
- case "omGetLatest": {
409
- const doc = await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", request.lookupKey)).order("desc").first();
410
- return { ok: true, result: doc ?? null };
411
- }
412
- case "omGetHistory": {
413
- let docs = await ctx.db.query(convexTable).withIndex("by_lookup_key", (q) => q.eq("lookupKey", request.lookupKey)).order("desc").take(OM_QUERY_MAX_DOCS);
414
- if (request.from) {
415
- docs = docs.filter((doc) => typeof doc.createdAt === "string" && doc.createdAt >= request.from);
416
- }
417
- if (request.to) {
418
- docs = docs.filter((doc) => typeof doc.createdAt === "string" && doc.createdAt <= request.to);
419
- }
420
- if (request.offset != null) {
421
- docs = docs.slice(request.offset);
422
- }
423
- return { ok: true, result: docs.slice(0, request.limit) };
424
- }
425
- case "omUpdateActive": {
426
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
427
- const safeTokenCount = Number.isFinite(request.tokenCount) && request.tokenCount >= 0 ? request.tokenCount : 0;
428
- await ctx.db.patch(doc._id, {
429
- activeObservations: request.observations,
430
- lastObservedAt: request.lastObservedAt,
431
- // Reset pending tokens since we've now observed them
432
- pendingMessageTokens: 0,
433
- observationTokenCount: safeTokenCount,
434
- totalTokensObserved: Number(doc.totalTokensObserved || 0) + safeTokenCount,
435
- observedMessageIds: request.observedMessageIds,
436
- updatedAt: request.updatedAt
437
- });
438
- return { ok: true };
439
- }
440
- case "omAppendBufferedChunk": {
441
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
442
- const chunks = parseStoredChunks(doc.bufferedObservationChunks);
443
- chunks.push(request.chunk);
444
- const patch = {
445
- bufferedObservationChunks: JSON.stringify(chunks),
446
- updatedAt: request.updatedAt
447
- };
448
- if (request.lastBufferedAtTime) {
449
- patch.lastBufferedAtTime = request.lastBufferedAtTime;
450
- }
451
- await ctx.db.patch(doc._id, patch);
452
- return { ok: true };
453
- }
454
- case "omSwapBuffered": {
455
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
456
- const persistedChunks = parseStoredChunks(doc.bufferedObservationChunks);
457
- if (persistedChunks.length === 0) {
458
- return { ok: true, result: EMPTY_SWAP_RESULT };
459
- }
460
- const chunks = Array.isArray(request.bufferedChunks) ? request.bufferedChunks : persistedChunks;
461
- if (chunks.length === 0) {
462
- return { ok: true, result: EMPTY_SWAP_RESULT };
463
- }
464
- const chunksToActivate = selectActivationBoundary(chunks, {
465
- activationRatio: request.activationRatio,
466
- messageTokensThreshold: request.messageTokensThreshold,
467
- currentPendingTokens: request.currentPendingTokens,
468
- forceMaxActivation: request.forceMaxActivation
469
- });
470
- const activatedChunks = chunks.slice(0, chunksToActivate);
471
- const remainingChunks = chunks.slice(chunksToActivate);
472
- const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
473
- const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
474
- const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
475
- const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + (c.messageIds?.length ?? 0), 0);
476
- const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
477
- const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
478
- const latestChunk = activatedChunks[activatedChunks.length - 1];
479
- const lastObservedAt = request.lastObservedAt ?? latestChunk?.lastObservedAt ?? request.now;
480
- const existingActive = doc.activeObservations || "";
481
- const boundary = `
482
-
483
- --- message boundary (${lastObservedAt}) ---
484
-
485
- `;
486
- const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
487
- await ctx.db.patch(doc._id, {
488
- activeObservations: newActive,
489
- observationTokenCount: Number(doc.observationTokenCount || 0) + activatedTokens,
490
- // Decrement pending message tokens (clamped to zero)
491
- pendingMessageTokens: Math.max(0, Number(doc.pendingMessageTokens || 0) - activatedMessageTokens),
492
- bufferedObservationChunks: remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null,
493
- lastObservedAt,
494
- updatedAt: request.now
495
- });
496
- const latestChunkHints = activatedChunks[activatedChunks.length - 1];
497
- return {
498
- ok: true,
499
- result: {
500
- chunksActivated: activatedChunks.length,
501
- messageTokensActivated: activatedMessageTokens,
502
- observationTokensActivated: activatedTokens,
503
- messagesActivated: activatedMessageCount,
504
- activatedCycleIds,
505
- activatedMessageIds,
506
- observations: activatedContent,
507
- perChunk: activatedChunks.map((c) => ({
508
- cycleId: c.cycleId ?? "",
509
- messageTokens: c.messageTokens ?? 0,
510
- observationTokens: c.tokenCount,
511
- messageCount: c.messageIds?.length ?? 0,
512
- observations: c.observations
513
- })),
514
- suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
515
- currentTask: latestChunkHints?.currentTask ?? void 0
516
- }
517
- };
518
- }
519
- case "omUpdateBufferedReflection": {
520
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
521
- const existingContent = doc.bufferedReflection || "";
522
- await ctx.db.patch(doc._id, {
523
- bufferedReflection: existingContent ? `${existingContent}
524
-
525
- ${request.reflection}` : request.reflection,
526
- bufferedReflectionTokens: Number(doc.bufferedReflectionTokens || 0) + request.tokenCount,
527
- bufferedReflectionInputTokens: Number(doc.bufferedReflectionInputTokens || 0) + request.inputTokenCount,
528
- reflectedObservationLineCount: request.reflectedObservationLineCount,
529
- updatedAt: request.updatedAt
530
- });
531
- return { ok: true };
532
- }
533
- case "omSwapBufferedReflection": {
534
- const { currentRecord, newId, tokenCount, now } = request;
535
- const doc = requireRecord(await findRecordById(ctx, convexTable, currentRecord.id), currentRecord.id);
536
- const bufferedReflection = doc.bufferedReflection || "";
537
- if (!bufferedReflection) {
538
- throw new Error("No buffered reflection to swap");
539
- }
540
- const newObservations = mergeReflectionWithUnreflected(
541
- doc.activeObservations || "",
542
- bufferedReflection,
543
- Number(doc.reflectedObservationLineCount || 0)
544
- );
545
- const newRecord = {
546
- id: newId,
547
- lookupKey: currentRecord.lookupKey,
548
- scope: currentRecord.scope,
549
- resourceId: currentRecord.resourceId,
550
- threadId: currentRecord.threadId,
551
- activeObservations: newObservations,
552
- activeObservationsPendingUpdate: null,
553
- originType: "reflection",
554
- config: currentRecord.config,
555
- generationCount: currentRecord.generationCount + 1,
556
- lastObservedAt: currentRecord.lastObservedAt,
557
- lastReflectionAt: now,
558
- pendingMessageTokens: 0,
559
- totalTokensObserved: currentRecord.totalTokensObserved,
560
- observationTokenCount: tokenCount,
561
- isObserving: false,
562
- isReflecting: false,
563
- isBufferingObservation: false,
564
- isBufferingReflection: false,
565
- lastBufferedAtTokens: 0,
566
- lastBufferedAtTime: null,
567
- observedTimezone: currentRecord.observedTimezone,
568
- metadata: currentRecord.metadata,
569
- createdAt: now,
570
- updatedAt: now
571
- };
572
- await ctx.db.insert(convexTable, newRecord);
573
- await ctx.db.patch(doc._id, {
574
- bufferedReflection: null,
575
- bufferedReflectionTokens: null,
576
- bufferedReflectionInputTokens: null,
577
- reflectedObservationLineCount: null,
578
- updatedAt: now
579
- });
580
- return { ok: true, result: newRecord };
581
- }
582
- case "omUpdateConfig": {
583
- const doc = requireRecord(await findRecordById(ctx, convexTable, request.id), request.id);
584
- const existing = parseJsonObject(doc.config);
585
- const incoming = parseJsonObject(request.config);
586
- const merged = deepMergeOMConfig(existing, incoming);
587
- await ctx.db.patch(doc._id, {
588
- config: JSON.stringify(merged),
589
- updatedAt: request.updatedAt
590
- });
591
- return { ok: true };
592
- }
593
- }
594
- }
595
-
596
- // src/server/workflow-snapshot.ts
597
- var PENDING_MARKER_KEY = "__mastra_pending__";
598
- function isPendingMarker(val) {
599
- return val !== null && typeof val === "object" && Object.prototype.hasOwnProperty.call(val, PENDING_MARKER_KEY) && val[PENDING_MARKER_KEY] === true && Object.keys(val).length === 1;
600
- }
601
- function isSuspendedStepResult(val) {
602
- const result = val;
603
- return val !== null && typeof val === "object" && "status" in val && result?.status === "suspended" && ("suspendPayload" in val || "suspendedAt" in val);
604
- }
605
- function canResetWithPendingMarker(val) {
606
- if (val == null || isPendingMarker(val)) {
607
- return true;
608
- }
609
- return isSuspendedStepResult(val);
610
- }
611
- function createEmptyWorkflowSnapshot(runId) {
612
- return {
613
- context: {},
614
- activePaths: [],
615
- activeStepsPath: {},
616
- timestamp: Date.now(),
617
- suspendedPaths: {},
618
- resumeLabels: {},
619
- serializedStepGraph: [],
620
- value: {},
621
- waitingPaths: {},
622
- status: "pending",
623
- runId
624
- };
625
- }
626
- function mergeWorkflowStepResult({
627
- snapshot,
628
- stepId,
629
- result,
630
- requestContext
631
- }) {
632
- if (!snapshot?.context) {
633
- throw new Error(`Snapshot context not found for runId ${snapshot?.runId}`);
634
- }
635
- const existingResult = snapshot.context[stepId];
636
- if (existingResult && "output" in existingResult && Array.isArray(existingResult.output) && result && typeof result === "object" && "output" in result && Array.isArray(result.output)) {
637
- const existingOutput = existingResult.output;
638
- const newOutput = result.output;
639
- const mergedOutput = [...existingOutput];
640
- const hasPendingMarker = newOutput.some(isPendingMarker);
641
- for (let i = 0; i < Math.max(existingOutput.length, newOutput.length); i++) {
642
- if (i < newOutput.length) {
643
- const newVal = newOutput[i];
644
- if (isPendingMarker(newVal)) {
645
- if (i >= existingOutput.length || canResetWithPendingMarker(existingOutput[i])) {
646
- mergedOutput[i] = null;
647
- }
648
- } else if (newVal !== null && newVal !== void 0 && !hasPendingMarker) {
649
- mergedOutput[i] = newVal;
650
- } else if (i >= existingOutput.length) {
651
- mergedOutput[i] = null;
652
- }
653
- }
654
- }
655
- snapshot.context[stepId] = {
656
- ...existingResult,
657
- // Pending-marker writes are reset commands built from an earlier snapshot,
658
- // so keep existing step-level fields and ignore sibling values they carry.
659
- ...hasPendingMarker ? {} : result,
660
- output: mergedOutput
661
- };
662
- } else {
663
- snapshot.context[stepId] = result;
664
- }
665
- snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
666
- return JSON.parse(JSON.stringify(snapshot.context));
667
- }
668
-
669
- // src/server/storage.ts
670
- var TABLE_VECTOR_INDEXES = "mastra_vector_indexes";
671
- var VECTOR_TABLE_PREFIX = "mastra_vector_";
672
- var CONVEX_TABLE_WORKFLOW_SNAPSHOTS = "mastra_workflow_snapshots";
673
- var CONVEX_TABLE_BACKGROUND_TASKS = "mastra_background_tasks";
674
- var CONVEX_TABLE_DOCUMENTS = "mastra_documents";
675
- var CONVEX_TABLE_OBSERVATIONAL_MEMORY = "mastra_observational_memory";
676
- var STORAGE_MUTATION_BATCH_SIZE = 25;
677
- var LOAD_MANY_MAX_IDS_PER_REQUEST = 10;
678
- var DEFAULT_SCHEDULE_QUERY_LIMIT = 100;
679
- var BACKGROUND_TASK_FIELD_ALIASES = {
680
- tool_call_id: "toolCallId",
681
- toolCallId: "tool_call_id",
682
- tool_name: "toolName",
683
- toolName: "tool_name",
684
- agent_id: "agentId",
685
- agentId: "agent_id",
686
- run_id: "runId",
687
- runId: "run_id",
688
- thread_id: "threadId",
689
- threadId: "thread_id",
690
- resource_id: "resourceId",
691
- resourceId: "resource_id",
692
- suspend_payload: "suspendPayload",
693
- suspendPayload: "suspend_payload",
694
- retry_count: "retryCount",
695
- retryCount: "retry_count",
696
- max_retries: "maxRetries",
697
- maxRetries: "max_retries",
698
- timeout_ms: "timeoutMs",
699
- timeoutMs: "timeout_ms"
700
- };
701
- function normalizeScheduleQueryLimit(limit) {
702
- if (limit == null || !Number.isFinite(limit)) return DEFAULT_SCHEDULE_QUERY_LIMIT;
703
- return Math.max(0, Math.floor(limit));
704
- }
705
- function normalizeLoadManyIds(ids) {
706
- if (ids.length > LOAD_MANY_MAX_IDS_PER_REQUEST) {
707
- throw new Error(`loadMany supports at most ${LOAD_MANY_MAX_IDS_PER_REQUEST} ids per request`);
708
- }
709
- return [...new Set(ids)];
710
- }
711
- function applyConvexEqualityFilters(query, filters, indexedFields = /* @__PURE__ */ new Set()) {
712
- const remainingFilters = filters?.filter((filter) => !indexedFields.has(filter.field));
713
- if (!remainingFilters?.length) return query;
714
- return query.filter((q) => {
715
- const predicates = remainingFilters.map((filter) => q.eq(q.field(filter.field), filter.value));
716
- return predicates.length === 1 ? predicates[0] : q.and(...predicates);
717
- });
718
- }
719
- async function mapInBatches(inputs, batchSize, mapper) {
720
- const results = [];
721
- for (let index = 0; index < inputs.length; index += batchSize) {
722
- results.push(...await Promise.all(inputs.slice(index, index + batchSize).map(mapper)));
723
- }
724
- return results;
725
- }
726
- async function deleteDocs(ctx, docs) {
727
- await mapInBatches(docs, STORAGE_MUTATION_BATCH_SIZE, (doc) => ctx.db.delete(doc._id));
728
- }
729
- async function findExistingDocsByIds(ids, findDoc) {
730
- const docs = await mapInBatches([...new Set(ids)], STORAGE_MUTATION_BATCH_SIZE, findDoc);
731
- return docs.filter((doc) => Boolean(doc));
732
- }
733
- function isBackgroundTasksTable(convexTable, request) {
734
- return convexTable === CONVEX_TABLE_BACKGROUND_TASKS && request.tableName === constants.TABLE_BACKGROUND_TASKS;
735
- }
736
- function matchesFilters(record, filters) {
737
- return filters.every((filter) => {
738
- if (record[filter.field] === filter.value) return true;
739
- const alternateField = BACKGROUND_TASK_FIELD_ALIASES[filter.field];
740
- return alternateField ? record[alternateField] === filter.value : false;
741
- });
742
- }
743
- function mergeLegacyRecord(record, patch) {
744
- const merged = { ...record };
745
- for (const [field, value] of Object.entries(patch)) {
746
- const alternateField = BACKGROUND_TASK_FIELD_ALIASES[field];
747
- if (alternateField) delete merged[alternateField];
748
- merged[field] = value;
749
- }
750
- return merged;
751
- }
752
- function stripPatchKeys(record, keys) {
753
- const stripped = { ...record };
754
- for (const key of keys) delete stripped[key];
755
- return stripped;
756
- }
757
- function dedupeByRecordId(records) {
758
- const seen = /* @__PURE__ */ new Set();
759
- return records.filter((record) => {
760
- if (record?.id == null) return true;
761
- const id = String(record.id);
762
- if (seen.has(id)) return false;
763
- seen.add(id);
764
- return true;
765
- });
766
- }
767
- function isMissingBackgroundTaskSchemaError(error) {
768
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
769
- return message.includes(CONVEX_TABLE_BACKGROUND_TASKS) && (message.includes("does not exist") || message.includes("not found") || message.includes("not defined") || message.includes("no such"));
770
- }
771
- async function findGenericDocumentById(ctx, tableName, id) {
772
- return await ctx.db.query(CONVEX_TABLE_DOCUMENTS).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(id))).unique();
773
- }
774
- async function findGenericDocumentsByTable(ctx, tableName, limit) {
775
- return await ctx.db.query(CONVEX_TABLE_DOCUMENTS).withIndex("by_table", (q) => q.eq("table", tableName)).take(limit);
776
- }
777
- async function filterLegacyRecordsWithoutTypedCopy(ctx, convexTable, legacyRecords) {
778
- const records = await mapInBatches(legacyRecords, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
779
- if (record.id == null) return record;
780
- const typedDoc = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", String(record.id))).unique();
781
- return typedDoc ? null : record;
782
- });
783
- return records.filter((record) => Boolean(record));
784
- }
785
- function coalesceTypedRecordsForBatchInsert(records) {
786
- const recordsById = /* @__PURE__ */ new Map();
787
- for (const record of records) {
788
- const id = record.id;
789
- if (!id) continue;
790
- const key = String(id);
791
- recordsById.set(key, { ...recordsById.get(key) ?? {}, ...record });
792
- }
793
- return [...recordsById.values()];
794
- }
795
- function coalesceLastRecordById(records) {
796
- const recordsById = /* @__PURE__ */ new Map();
797
- for (const record of records) {
798
- const id = record.id;
799
- if (!id) continue;
800
- recordsById.set(String(id), record);
801
- }
802
- return [...recordsById.values()];
803
- }
804
- function resolveTable(tableName) {
805
- switch (tableName) {
806
- case constants.TABLE_THREADS:
807
- return { convexTable: "mastra_threads", isTyped: true };
808
- case constants.TABLE_MESSAGES:
809
- return { convexTable: "mastra_messages", isTyped: true };
810
- case constants.TABLE_RESOURCES:
811
- return { convexTable: "mastra_resources", isTyped: true };
812
- case constants.TABLE_WORKFLOW_SNAPSHOT:
813
- return { convexTable: CONVEX_TABLE_WORKFLOW_SNAPSHOTS, isTyped: true };
814
- case constants.TABLE_SCORERS:
815
- return { convexTable: "mastra_scorers", isTyped: true };
816
- case constants.TABLE_SCHEDULES:
817
- return { convexTable: "mastra_schedules", isTyped: true };
818
- case constants.TABLE_SCHEDULE_TRIGGERS:
819
- return { convexTable: "mastra_schedule_triggers", isTyped: true };
820
- case constants.TABLE_CHANNEL_INSTALLATIONS:
821
- return { convexTable: "mastra_channel_installations", isTyped: true };
822
- case constants.TABLE_CHANNEL_CONFIG:
823
- return { convexTable: "mastra_channel_config", isTyped: true };
824
- case constants.TABLE_BACKGROUND_TASKS:
825
- return { convexTable: CONVEX_TABLE_BACKGROUND_TASKS, isTyped: true };
826
- case TABLE_VECTOR_INDEXES:
827
- return { convexTable: "mastra_vector_indexes", isTyped: true };
828
- case CONVEX_TABLE_OBSERVATIONAL_MEMORY:
829
- return { convexTable: CONVEX_TABLE_OBSERVATIONAL_MEMORY, isTyped: true };
830
- default:
831
- if (tableName.startsWith(VECTOR_TABLE_PREFIX)) {
832
- return { convexTable: "mastra_vectors", isTyped: true };
833
- }
834
- return { convexTable: "mastra_documents", isTyped: false };
835
- }
836
- }
837
- var mastraStorage = server.mutationGeneric(async (ctx, request) => {
838
- try {
839
- const { convexTable, isTyped } = resolveTable(request.tableName);
840
- if (request.tableName.startsWith(VECTOR_TABLE_PREFIX) && request.tableName !== TABLE_VECTOR_INDEXES) {
841
- return await handleVectorOperation(ctx, request);
842
- }
843
- if (isTyped) {
844
- if (isBackgroundTasksTable(convexTable, request)) {
845
- try {
846
- return await handleTypedOperation(ctx, convexTable, request);
847
- } catch (error) {
848
- if (!isMissingBackgroundTaskSchemaError(error)) throw error;
849
- return handleGenericOperation(ctx, request);
850
- }
851
- }
852
- return handleTypedOperation(ctx, convexTable, request);
853
- }
854
- return handleGenericOperation(ctx, request);
855
- } catch (error) {
856
- const err = error;
857
- return {
858
- ok: false,
859
- error: err.message
860
- };
861
- }
862
- });
863
- function parseStoredSnapshot(stored, runId) {
864
- if (typeof stored === "string") return JSON.parse(stored);
865
- return JSON.parse(JSON.stringify(stored ?? createEmptyWorkflowSnapshot(runId)));
866
- }
867
- function parseMetadataForMerge(metadata) {
868
- if (metadata == null) return {};
869
- if (typeof metadata === "string") {
870
- try {
871
- const parsed = JSON.parse(metadata);
872
- return parseMetadataForMerge(parsed);
873
- } catch {
874
- return {};
875
- }
876
- }
877
- if (typeof metadata === "object" && !Array.isArray(metadata)) {
878
- return metadata;
879
- }
880
- return {};
881
- }
882
- function mergeMetadata(existing, update) {
883
- return {
884
- ...parseMetadataForMerge(existing),
885
- ...update ?? {}
886
- };
887
- }
888
- async function handleTypedOperation(ctx, convexTable, request) {
889
- switch (request.op) {
890
- case "omGetLatest":
891
- case "omGetHistory":
892
- case "omUpdateActive":
893
- case "omAppendBufferedChunk":
894
- case "omSwapBuffered":
895
- case "omUpdateBufferedReflection":
896
- case "omSwapBufferedReflection":
897
- case "omUpdateConfig": {
898
- if (convexTable !== CONVEX_TABLE_OBSERVATIONAL_MEMORY) {
899
- throw new Error(`${request.op} is only supported for ${CONVEX_TABLE_OBSERVATIONAL_MEMORY}`);
900
- }
901
- return handleObservationalMemoryOperation(ctx, convexTable, request);
902
- }
903
- case "createSchedule": {
904
- if (convexTable !== "mastra_schedules") {
905
- throw new Error(`createSchedule is only supported for mastra_schedules`);
906
- }
907
- const record = request.record;
908
- const id = record.id;
909
- if (!id) {
910
- throw new Error(`Schedule is missing an id`);
911
- }
912
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
913
- if (existing) {
914
- throw new Error(`Schedule with id "${id}" already exists`);
915
- }
916
- await ctx.db.insert(convexTable, record);
917
- return { ok: true };
918
- }
919
- case "recordScheduleTrigger": {
920
- if (convexTable !== "mastra_schedule_triggers") {
921
- throw new Error(`recordScheduleTrigger is only supported for mastra_schedule_triggers`);
922
- }
923
- const record = request.record;
924
- const id = record.id;
925
- if (!id) {
926
- throw new Error(`Schedule trigger is missing an id`);
927
- }
928
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
929
- if (existing) {
930
- throw new Error(`Schedule trigger with id "${id}" already exists`);
931
- }
932
- await ctx.db.insert(convexTable, record);
933
- return { ok: true };
934
- }
935
- case "listDueSchedules": {
936
- if (convexTable !== "mastra_schedules") {
937
- throw new Error(`listDueSchedules is only supported for mastra_schedules`);
938
- }
939
- const query = ctx.db.query(convexTable).withIndex("by_status_next_fire_at", (q) => q.eq("status", "active").lte("next_fire_at", request.now));
940
- const docs = await query.take(normalizeScheduleQueryLimit(request.limit));
941
- return { ok: true, result: docs };
942
- }
943
- case "updateScheduleNextFire": {
944
- if (convexTable !== "mastra_schedules") {
945
- throw new Error(`updateScheduleNextFire is only supported for mastra_schedules`);
946
- }
947
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
948
- if (!existing || existing.status !== "active" || existing.next_fire_at !== request.expectedNextFireAt) {
949
- return { ok: true, result: false };
950
- }
951
- await ctx.db.patch(existing._id, {
952
- next_fire_at: request.newNextFireAt,
953
- last_fire_at: request.lastFireAt,
954
- last_run_id: request.lastRunId,
955
- updated_at: Date.now()
956
- });
957
- return { ok: true, result: true };
958
- }
959
- case "updateSchedule": {
960
- if (convexTable !== "mastra_schedules") {
961
- throw new Error(`updateSchedule is only supported for mastra_schedules`);
962
- }
963
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
964
- if (!existing) {
965
- throw new Error(`Schedule ${request.id} not found`);
966
- }
967
- await ctx.db.patch(existing._id, request.patch);
968
- return { ok: true, result: { ...existing, ...request.patch } };
969
- }
970
- case "listScheduleTriggers": {
971
- if (convexTable !== "mastra_schedule_triggers") {
972
- throw new Error(`listScheduleTriggers is only supported for mastra_schedule_triggers`);
973
- }
974
- const query = ctx.db.query(convexTable).withIndex("by_schedule_actual", (q) => {
975
- let builder = q.eq("schedule_id", request.scheduleId);
976
- if (request.fromActualFireAt != null) {
977
- builder = builder.gte("actual_fire_at", request.fromActualFireAt);
978
- }
979
- if (request.toActualFireAt != null) {
980
- builder = builder.lt("actual_fire_at", request.toActualFireAt);
981
- }
982
- return builder;
983
- }).order("desc");
984
- const docs = await query.take(normalizeScheduleQueryLimit(request.limit));
985
- return { ok: true, result: docs };
986
- }
987
- case "deleteScheduleTriggers": {
988
- if (convexTable !== "mastra_schedule_triggers") {
989
- throw new Error(`deleteScheduleTriggers is only supported for mastra_schedule_triggers`);
990
- }
991
- const docs = await ctx.db.query(convexTable).withIndex("by_schedule_actual", (q) => q.eq("schedule_id", request.scheduleId)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
992
- const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
993
- const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
994
- await deleteDocs(ctx, docsToDelete);
995
- return { ok: true, hasMore };
996
- }
997
- case "insert": {
998
- const record = request.record;
999
- const id = record.id;
1000
- if (!id) {
1001
- throw new Error(`Record is missing an id`);
1002
- }
1003
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
1004
- if (existing) {
1005
- const { id: _, ...updateData } = record;
1006
- await ctx.db.patch(existing._id, updateData);
1007
- } else {
1008
- await ctx.db.insert(convexTable, record);
1009
- }
1010
- return { ok: true };
1011
- }
1012
- case "batchInsert": {
1013
- const records = coalesceTypedRecordsForBatchInsert(request.records);
1014
- await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
1015
- const id = record.id;
1016
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
1017
- if (existing) {
1018
- const { id: _, ...updateData } = record;
1019
- await ctx.db.patch(existing._id, updateData);
1020
- } else {
1021
- await ctx.db.insert(convexTable, record);
1022
- }
1023
- });
1024
- return { ok: true };
1025
- }
1026
- case "updateThread": {
1027
- if (convexTable !== "mastra_threads") {
1028
- return { ok: false, error: `Unsupported operation ${request.op} for table ${request.tableName}` };
1029
- }
1030
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
1031
- if (!existing) {
1032
- return { ok: true, result: null };
1033
- }
1034
- const patchRecord = {
1035
- title: request.title,
1036
- metadata: mergeMetadata(existing.metadata, request.metadata),
1037
- updatedAt: request.updatedAt
1038
- };
1039
- await ctx.db.patch(existing._id, patchRecord);
1040
- return { ok: true, result: { ...existing, ...patchRecord } };
1041
- }
1042
- case "updateResource": {
1043
- if (convexTable !== "mastra_resources") {
1044
- return { ok: false, error: `Unsupported operation ${request.op} for table ${request.tableName}` };
1045
- }
1046
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.resourceId)).unique();
1047
- if (!existing) {
1048
- const record = {
1049
- id: request.resourceId,
1050
- ...request.workingMemory !== void 0 ? { workingMemory: request.workingMemory } : {},
1051
- metadata: request.metadata ?? {},
1052
- createdAt: request.createdAt,
1053
- updatedAt: request.updatedAt
1054
- };
1055
- await ctx.db.insert(convexTable, record);
1056
- return { ok: true, result: record };
1057
- }
1058
- const patchRecord = {
1059
- updatedAt: request.updatedAt
1060
- };
1061
- if (request.workingMemory !== void 0) {
1062
- patchRecord.workingMemory = request.workingMemory;
1063
- }
1064
- if (request.metadata !== void 0) {
1065
- patchRecord.metadata = mergeMetadata(existing.metadata, request.metadata);
1066
- }
1067
- await ctx.db.patch(existing._id, patchRecord);
1068
- return { ok: true, result: { ...existing, ...patchRecord } };
1069
- }
1070
- case "patch": {
1071
- const patchRecord = stripPatchKeys(request.record, ["id"]);
1072
- const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
1073
- if (!existing) {
1074
- if (isBackgroundTasksTable(convexTable, request)) {
1075
- const legacy = await findGenericDocumentById(ctx, request.tableName, request.id);
1076
- if (legacy) {
1077
- await ctx.db.patch(legacy._id, { record: mergeLegacyRecord(legacy.record, patchRecord) });
1078
- return { ok: true, result: true };
1079
- }
1080
- }
1081
- return { ok: true, result: false };
1082
- }
1083
- await ctx.db.patch(existing._id, patchRecord);
1084
- if (isBackgroundTasksTable(convexTable, request)) {
1085
- const legacy = await findGenericDocumentById(ctx, request.tableName, request.id);
1086
- if (legacy) {
1087
- await ctx.db.delete(legacy._id);
1088
- }
1089
- }
1090
- return { ok: true, result: true };
1091
- }
1092
- case "load": {
1093
- const keys = request.keys;
1094
- if (keys.id) {
1095
- const doc = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", keys.id)).unique();
1096
- if (!doc && isBackgroundTasksTable(convexTable, request)) {
1097
- const legacy = await findGenericDocumentById(ctx, request.tableName, String(keys.id));
1098
- return { ok: true, result: legacy?.record ?? null };
1099
- }
1100
- return { ok: true, result: doc || null };
1101
- }
1102
- if (convexTable === CONVEX_TABLE_WORKFLOW_SNAPSHOTS && typeof keys.workflow_name === "string" && typeof keys.run_id === "string") {
1103
- const doc = await ctx.db.query(convexTable).withIndex("by_workflow_run", (q) => q.eq("workflow_name", keys.workflow_name).eq("run_id", keys.run_id)).unique();
1104
- return { ok: true, result: doc || null };
1105
- }
1106
- const docs = await ctx.db.query(convexTable).take(1e4);
1107
- const match = docs.find((doc) => Object.entries(keys).every(([key, value]) => doc[key] === value));
1108
- return { ok: true, result: match || null };
1109
- }
1110
- case "loadMany": {
1111
- const ids = normalizeLoadManyIds(request.ids);
1112
- const docs = await mapInBatches(
1113
- ids,
1114
- STORAGE_MUTATION_BATCH_SIZE,
1115
- (id) => ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique()
1116
- );
1117
- const typedDocs = docs.filter(Boolean);
1118
- if (!isBackgroundTasksTable(convexTable, request)) {
1119
- return { ok: true, result: typedDocs };
1120
- }
1121
- const typedDocsById = new Map(typedDocs.map((doc) => [String(doc.id), doc]));
1122
- const legacyDocs = await mapInBatches(
1123
- ids.filter((id) => !typedDocsById.has(id)),
1124
- STORAGE_MUTATION_BATCH_SIZE,
1125
- (id) => findGenericDocumentById(ctx, request.tableName, id)
1126
- );
1127
- const legacyRecordsById = new Map(
1128
- legacyDocs.filter((doc) => Boolean(doc)).map((doc) => [String(doc.record.id), doc.record])
1129
- );
1130
- return {
1131
- ok: true,
1132
- result: ids.map((id) => typedDocsById.get(id) ?? legacyRecordsById.get(id)).filter(Boolean)
1133
- };
1134
- }
1135
- case "queryTable": {
1136
- const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
1137
- let query;
1138
- let indexedFields = /* @__PURE__ */ new Set();
1139
- if (request.indexHint) {
1140
- const hint = request.indexHint;
1141
- if (hint.index === "by_workflow") {
1142
- query = ctx.db.query(convexTable).withIndex("by_workflow", (q) => q.eq("workflow_name", hint.workflowName));
1143
- } else if (hint.index === "by_workflow_run") {
1144
- query = ctx.db.query(convexTable).withIndex(
1145
- "by_workflow_run",
1146
- (q) => q.eq("workflow_name", hint.workflowName).eq("run_id", hint.runId)
1147
- );
1148
- } else {
1149
- query = ctx.db.query(convexTable);
1150
- }
1151
- } else if (request.filters && request.filters.length > 0) {
1152
- const match = findBestIndex(convexTable, request.filters);
1153
- if (match) {
1154
- query = ctx.db.query(convexTable).withIndex(match.indexName, (q) => {
1155
- let builder = q;
1156
- for (const filter of match.indexedFilters) {
1157
- builder = builder.eq(filter.field, filter.value);
1158
- }
1159
- return builder;
1160
- });
1161
- indexedFields = new Set(match.indexedFilters.map((filter) => filter.field));
1162
- } else {
1163
- query = ctx.db.query(convexTable);
1164
- }
1165
- } else {
1166
- query = ctx.db.query(convexTable);
1167
- }
1168
- let docs = await applyConvexEqualityFilters(query, request.filters, indexedFields).take(maxDocs);
1169
- if (isBackgroundTasksTable(convexTable, request)) {
1170
- const legacyDocs = await findGenericDocumentsByTable(ctx, request.tableName, maxDocs);
1171
- let legacyRecords = legacyDocs.map((doc) => doc.record);
1172
- if (request.filters && request.filters.length > 0) {
1173
- legacyRecords = legacyRecords.filter((record) => matchesFilters(record, request.filters));
1174
- }
1175
- legacyRecords = await filterLegacyRecordsWithoutTypedCopy(ctx, convexTable, legacyRecords);
1176
- docs.push(...legacyRecords);
1177
- docs = dedupeByRecordId(docs);
1178
- }
1179
- if (request.limit) {
1180
- docs = docs.slice(0, request.limit);
1181
- }
1182
- return { ok: true, result: docs };
1183
- }
1184
- case "clearTable":
1185
- case "dropTable": {
1186
- const docs = await ctx.db.query(convexTable).take(STORAGE_MUTATION_BATCH_SIZE + 1);
1187
- const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
1188
- let docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
1189
- let legacyHasMore = false;
1190
- if (!hasMore && docsToDelete.length < STORAGE_MUTATION_BATCH_SIZE && isBackgroundTasksTable(convexTable, request)) {
1191
- const remainingBatchSize = STORAGE_MUTATION_BATCH_SIZE - docsToDelete.length;
1192
- const legacyDocs = await findGenericDocumentsByTable(ctx, request.tableName, remainingBatchSize + 1);
1193
- legacyHasMore = legacyDocs.length > remainingBatchSize;
1194
- docsToDelete = docsToDelete.concat(legacyHasMore ? legacyDocs.slice(0, remainingBatchSize) : legacyDocs);
1195
- }
1196
- await deleteDocs(ctx, docsToDelete);
1197
- return { ok: true, hasMore: hasMore || legacyHasMore };
1198
- }
1199
- case "deleteMany": {
1200
- const docsToDelete = await findExistingDocsByIds(
1201
- request.ids,
1202
- (id) => ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique()
1203
- );
1204
- if (isBackgroundTasksTable(convexTable, request)) {
1205
- docsToDelete.push(
1206
- ...await findExistingDocsByIds(request.ids, (id) => findGenericDocumentById(ctx, request.tableName, id))
1207
- );
1208
- }
1209
- await deleteDocs(ctx, docsToDelete);
1210
- return { ok: true };
1211
- }
1212
- case "mergeWorkflowStepResult": {
1213
- if (convexTable !== CONVEX_TABLE_WORKFLOW_SNAPSHOTS) {
1214
- return { ok: false, error: `Unsupported operation ${request.op} for table ${request.tableName}` };
1215
- }
1216
- const existing = await ctx.db.query(convexTable).withIndex(
1217
- "by_workflow_run",
1218
- (q) => q.eq("workflow_name", request.workflowName).eq("run_id", request.runId)
1219
- ).unique();
1220
- if (!existing) {
1221
- return { ok: false, error: `Workflow snapshot not found for runId ${request.runId}` };
1222
- }
1223
- const snapshot = parseStoredSnapshot(existing.snapshot, request.runId);
1224
- if (!snapshot.context) {
1225
- return { ok: false, error: `Snapshot for runId ${request.runId} is missing or has invalid context` };
1226
- }
1227
- const context = mergeWorkflowStepResult({
1228
- snapshot,
1229
- stepId: request.stepId,
1230
- result: JSON.parse(request.result),
1231
- requestContext: JSON.parse(request.requestContext)
1232
- });
1233
- await ctx.db.patch(existing._id, {
1234
- snapshot: JSON.stringify(snapshot),
1235
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1236
- });
1237
- return { ok: true, result: JSON.stringify(context) };
1238
- }
1239
- case "mergeWorkflowState": {
1240
- if (convexTable !== CONVEX_TABLE_WORKFLOW_SNAPSHOTS) {
1241
- return { ok: false, error: `Unsupported operation ${request.op} for table ${request.tableName}` };
1242
- }
1243
- const existing = await ctx.db.query(convexTable).withIndex(
1244
- "by_workflow_run",
1245
- (q) => q.eq("workflow_name", request.workflowName).eq("run_id", request.runId)
1246
- ).unique();
1247
- if (!existing) {
1248
- return { ok: false, error: `Workflow snapshot not found for runId ${request.runId}` };
1249
- }
1250
- const snapshot = parseStoredSnapshot(existing.snapshot, request.runId);
1251
- if (!snapshot.context) {
1252
- return { ok: false, error: `Snapshot for runId ${request.runId} is missing or has invalid context` };
1253
- }
1254
- const mergedSnapshot = { ...snapshot, ...JSON.parse(request.opts) };
1255
- await ctx.db.patch(existing._id, {
1256
- snapshot: JSON.stringify(mergedSnapshot),
1257
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1258
- });
1259
- return { ok: true, result: JSON.stringify(mergedSnapshot) };
1260
- }
1261
- default:
1262
- return { ok: false, error: `Unsupported operation ${request.op}` };
1263
- }
1264
- }
1265
- async function handleVectorOperation(ctx, request) {
1266
- const indexName = request.tableName.replace(VECTOR_TABLE_PREFIX, "");
1267
- const convexTable = "mastra_vectors";
1268
- switch (request.op) {
1269
- case "insert": {
1270
- const record = request.record;
1271
- const id = record.id;
1272
- if (!id) {
1273
- throw new Error(`Vector record is missing an id`);
1274
- }
1275
- const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique();
1276
- if (existing) {
1277
- await ctx.db.patch(existing._id, {
1278
- embedding: record.embedding,
1279
- metadata: record.metadata
1280
- });
1281
- } else {
1282
- await ctx.db.insert(convexTable, {
1283
- id,
1284
- indexName,
1285
- embedding: record.embedding,
1286
- metadata: record.metadata
1287
- });
1288
- }
1289
- return { ok: true };
1290
- }
1291
- case "batchInsert": {
1292
- const records = coalesceLastRecordById(request.records);
1293
- await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
1294
- const id = record.id;
1295
- const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique();
1296
- if (existing) {
1297
- await ctx.db.patch(existing._id, {
1298
- embedding: record.embedding,
1299
- metadata: record.metadata
1300
- });
1301
- } else {
1302
- await ctx.db.insert(convexTable, {
1303
- id,
1304
- indexName,
1305
- embedding: record.embedding,
1306
- metadata: record.metadata
1307
- });
1308
- }
1309
- });
1310
- return { ok: true };
1311
- }
1312
- case "patch": {
1313
- const patchRecord = stripPatchKeys(request.record, ["id", "indexName"]);
1314
- const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", request.id)).unique();
1315
- if (!existing) {
1316
- return { ok: true, result: false };
1317
- }
1318
- await ctx.db.patch(existing._id, patchRecord);
1319
- return { ok: true, result: true };
1320
- }
1321
- case "load": {
1322
- const keys = request.keys;
1323
- if (keys.id) {
1324
- const doc = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", keys.id)).unique();
1325
- return { ok: true, result: doc || null };
1326
- }
1327
- return { ok: true, result: null };
1328
- }
1329
- case "loadMany": {
1330
- const docs = await findExistingDocsByIds(
1331
- normalizeLoadManyIds(request.ids),
1332
- (id) => ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique()
1333
- );
1334
- return { ok: true, result: docs };
1335
- }
1336
- case "queryTable": {
1337
- if (request.cursor !== void 0 && request.pageSize === void 0) {
1338
- throw new Error("queryTable cursor requires pageSize");
1339
- }
1340
- if (request.pageSize !== void 0) {
1341
- if (!Number.isInteger(request.pageSize) || request.pageSize <= 0) {
1342
- throw new Error("queryTable pageSize must be a positive integer");
1343
- }
1344
- if (request.limit !== void 0) {
1345
- throw new Error("queryTable limit cannot be combined with pageSize");
1346
- }
1347
- const page = await ctx.db.query(convexTable).withIndex("by_index", (q) => q.eq("indexName", indexName)).paginate({ cursor: request.cursor ?? null, numItems: request.pageSize });
1348
- let docs2 = page.page;
1349
- if (request.filters && request.filters.length > 0) {
1350
- docs2 = docs2.filter((doc) => request.filters.every((filter) => doc[filter.field] === filter.value));
1351
- }
1352
- return {
1353
- ok: true,
1354
- result: docs2,
1355
- hasMore: !page.isDone,
1356
- continuationCursor: page.continueCursor
1357
- };
1358
- }
1359
- const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
1360
- let docs = await ctx.db.query(convexTable).withIndex("by_index", (q) => q.eq("indexName", indexName)).take(maxDocs);
1361
- if (request.filters && request.filters.length > 0) {
1362
- docs = docs.filter((doc) => request.filters.every((filter) => doc[filter.field] === filter.value));
1363
- }
1364
- if (request.limit) {
1365
- docs = docs.slice(0, request.limit);
1366
- }
1367
- return { ok: true, result: docs };
1368
- }
1369
- case "clearTable":
1370
- case "dropTable": {
1371
- const docs = await ctx.db.query(convexTable).withIndex("by_index", (q) => q.eq("indexName", indexName)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
1372
- const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
1373
- const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
1374
- await deleteDocs(ctx, docsToDelete);
1375
- return { ok: true, hasMore };
1376
- }
1377
- case "deleteMany": {
1378
- const docsToDelete = await findExistingDocsByIds(
1379
- request.ids,
1380
- (id) => (
1381
- // Use composite key (indexName, id) to scope deletion per index
1382
- ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique()
1383
- )
1384
- );
1385
- await deleteDocs(ctx, docsToDelete);
1386
- return { ok: true };
1387
- }
1388
- default:
1389
- return { ok: false, error: `Unsupported operation ${request.op}` };
1390
- }
1391
- }
1392
- async function handleGenericOperation(ctx, request) {
1393
- const tableName = request.tableName;
1394
- const convexTable = "mastra_documents";
1395
- switch (request.op) {
1396
- case "insert": {
1397
- const record = request.record;
1398
- if (!record.id) {
1399
- throw new Error(`Record for table ${tableName} is missing an id`);
1400
- }
1401
- const primaryKey = String(record.id);
1402
- const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", primaryKey)).unique();
1403
- if (existing) {
1404
- await ctx.db.patch(existing._id, { record });
1405
- } else {
1406
- await ctx.db.insert(convexTable, {
1407
- table: tableName,
1408
- primaryKey,
1409
- record
1410
- });
1411
- }
1412
- return { ok: true };
1413
- }
1414
- case "batchInsert": {
1415
- const records = coalesceLastRecordById(request.records);
1416
- await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
1417
- const primaryKey = String(record.id);
1418
- const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", primaryKey)).unique();
1419
- if (existing) {
1420
- await ctx.db.patch(existing._id, { record });
1421
- } else {
1422
- await ctx.db.insert(convexTable, {
1423
- table: tableName,
1424
- primaryKey,
1425
- record
1426
- });
1427
- }
1428
- });
1429
- return { ok: true };
1430
- }
1431
- case "patch": {
1432
- const patchRecord = stripPatchKeys(request.record, ["id"]);
1433
- const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(request.id))).unique();
1434
- if (!existing) {
1435
- return { ok: true, result: false };
1436
- }
1437
- await ctx.db.patch(existing._id, {
1438
- record: tableName === constants.TABLE_BACKGROUND_TASKS ? mergeLegacyRecord(existing.record, patchRecord) : { ...existing.record, ...patchRecord }
1439
- });
1440
- return { ok: true, result: true };
1441
- }
1442
- case "load": {
1443
- const keys = request.keys;
1444
- if (keys.id) {
1445
- const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(keys.id))).unique();
1446
- return { ok: true, result: existing ? existing.record : null };
1447
- }
1448
- const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(1e4);
1449
- const match = docs.find((doc) => Object.entries(keys).every(([key, value]) => doc.record?.[key] === value));
1450
- return { ok: true, result: match ? match.record : null };
1451
- }
1452
- case "loadMany": {
1453
- const docs = await mapInBatches(
1454
- normalizeLoadManyIds(request.ids),
1455
- STORAGE_MUTATION_BATCH_SIZE,
1456
- (id) => findGenericDocumentById(ctx, tableName, id)
1457
- );
1458
- return {
1459
- ok: true,
1460
- result: docs.filter((doc) => Boolean(doc)).map((doc) => doc.record)
1461
- };
1462
- }
1463
- case "queryTable": {
1464
- const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
1465
- const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(maxDocs);
1466
- let records = docs.map((doc) => doc.record);
1467
- if (request.filters && request.filters.length > 0) {
1468
- records = records.filter(
1469
- (record) => tableName === constants.TABLE_BACKGROUND_TASKS ? matchesFilters(record, request.filters) : request.filters.every((filter) => record?.[filter.field] === filter.value)
1470
- );
1471
- }
1472
- if (request.limit) {
1473
- records = records.slice(0, request.limit);
1474
- }
1475
- return { ok: true, result: records };
1476
- }
1477
- case "clearTable":
1478
- case "dropTable": {
1479
- const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
1480
- const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
1481
- const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
1482
- await deleteDocs(ctx, docsToDelete);
1483
- return { ok: true, hasMore };
1484
- }
1485
- case "deleteMany": {
1486
- const docsToDelete = await findExistingDocsByIds(
1487
- request.ids,
1488
- (id) => ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(id))).unique()
1489
- );
1490
- await deleteDocs(ctx, docsToDelete);
1491
- return { ok: true };
1492
- }
1493
- default:
1494
- return { ok: false, error: `Unsupported operation ${request.op}` };
1495
- }
1496
- }
1497
- var DEFAULT_ID_FIELD = "id";
1498
- var DEFAULT_ID_INDEX = "by_record_id";
1499
- var DEFAULT_VECTOR_FIELD = "embedding";
1500
- var DEFAULT_METADATA_FIELD = "metadata";
1501
- var NATIVE_VECTOR_BATCH_SIZE = 25;
1502
- var MAX_CONVEX_VECTOR_RESULTS = 256;
1503
- var nativeVectorFilterValueValidator = values.v.union(values.v.string(), values.v.number(), values.v.boolean(), values.v.null());
1504
- var nativeVectorFilterClauseValidator = values.v.object({
1505
- field: values.v.string(),
1506
- value: nativeVectorFilterValueValidator
1507
- });
1508
- var nativeVectorFilterValidator = values.v.union(
1509
- nativeVectorFilterClauseValidator,
1510
- values.v.object({ $or: values.v.array(nativeVectorFilterClauseValidator) })
1511
- );
1512
- var nativeVectorIndexConfigValidator = values.v.object({
1513
- tableName: values.v.string(),
1514
- vectorIndexName: values.v.string(),
1515
- dimension: values.v.optional(values.v.number()),
1516
- idField: values.v.optional(values.v.string()),
1517
- idIndexName: values.v.optional(values.v.string()),
1518
- vectorField: values.v.optional(values.v.string()),
1519
- metadataField: values.v.optional(values.v.string()),
1520
- filterFields: values.v.optional(values.v.array(values.v.string()))
1521
- });
1522
- function idField(config) {
1523
- return config.idField ?? DEFAULT_ID_FIELD;
1524
- }
1525
- function idIndexName(config) {
1526
- return config.idIndexName ?? DEFAULT_ID_INDEX;
1527
- }
1528
- function vectorField(config) {
1529
- return config.vectorField ?? DEFAULT_VECTOR_FIELD;
1530
- }
1531
- function metadataField(config) {
1532
- return config.metadataField ?? DEFAULT_METADATA_FIELD;
1533
- }
1534
- function asTableName(tableName) {
1535
- return tableName;
1536
- }
1537
- function asConvexId(id) {
1538
- return id;
1539
- }
1540
- function pickFilterFields(metadata, filterFields) {
1541
- const fields = {};
1542
- if (!metadata || !filterFields) return fields;
1543
- for (const field of filterFields) {
1544
- const value = metadata[field];
1545
- if (value !== void 0) {
1546
- fields[field] = value;
1547
- }
1548
- }
1549
- return fields;
1550
- }
1551
- function isMetadataRecord(value) {
1552
- return typeof value === "object" && value !== null && !Array.isArray(value);
1553
- }
1554
- function validateMetadataArray(metadata, idsLength) {
1555
- if (metadata === void 0) return void 0;
1556
- if (!Array.isArray(metadata)) {
1557
- throw new Error("Native vector upsert: metadata must be an array matching ids when provided");
1558
- }
1559
- if (metadata.length !== idsLength) {
1560
- throw new Error(`Native vector upsert: metadata length (${metadata.length}) must match ids length (${idsLength})`);
1561
- }
1562
- if (!metadata.every(isMetadataRecord)) {
1563
- throw new Error("Native vector upsert: metadata entries must be objects when provided");
1564
- }
1565
- return metadata;
1566
- }
1567
- function validateMetadataRecord(metadata) {
1568
- if (metadata === void 0) return void 0;
1569
- if (!isMetadataRecord(metadata)) {
1570
- throw new Error("Native vector update: metadata must be an object when provided");
1571
- }
1572
- return metadata;
1573
- }
1574
- function clearMissingFilterFields(patch, metadata, filterFields) {
1575
- if (!filterFields) return;
1576
- for (const field of filterFields) {
1577
- if (metadata[field] === void 0) {
1578
- patch[field] = void 0;
1579
- }
1580
- }
1581
- }
1582
- function omitVectorField(doc, config) {
1583
- const { [vectorField(config)]: _, ...docWithoutVector } = doc;
1584
- return docWithoutVector;
1585
- }
1586
- function buildRecord({
1587
- config,
1588
- id,
1589
- vector,
1590
- metadata
1591
- }) {
1592
- return {
1593
- [idField(config)]: id,
1594
- [vectorField(config)]: vector,
1595
- ...metadata !== void 0 ? { [metadataField(config)]: metadata } : {},
1596
- ...pickFilterFields(metadata, config.filterFields)
1597
- };
1598
- }
1599
- async function findByRecordId(ctx, config, id) {
1600
- return ctx.db.query(asTableName(config.tableName)).withIndex(idIndexName(config), (q) => q.eq(idField(config), id)).unique();
1601
- }
1602
- async function mapInBatches2(inputs, mapper) {
1603
- const results = [];
1604
- for (let index = 0; index < inputs.length; index += NATIVE_VECTOR_BATCH_SIZE) {
1605
- results.push(
1606
- ...await Promise.all(
1607
- inputs.slice(index, index + NATIVE_VECTOR_BATCH_SIZE).map((input, batchIndex) => mapper(input, index + batchIndex))
1608
- )
1609
- );
1610
- }
1611
- return results;
1612
- }
1613
- function buildVectorFilter(q, filter) {
1614
- if (!filter) return void 0;
1615
- if ("$or" in filter) {
1616
- return q.or(...filter.$or.map((clause) => q.eq(clause.field, clause.value)));
1617
- }
1618
- return q.eq(filter.field, filter.value);
1619
- }
1620
- var mastraNativeVectorAction = server.actionGeneric({
1621
- args: {
1622
- config: nativeVectorIndexConfigValidator,
1623
- vector: values.v.array(values.v.number()),
1624
- limit: values.v.optional(values.v.number()),
1625
- filter: values.v.optional(nativeVectorFilterValidator)
1626
- },
1627
- handler: async (ctx, args) => {
1628
- const config = args.config;
1629
- const limit = args.limit;
1630
- const filter = args.filter;
1631
- if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1 || limit > MAX_CONVEX_VECTOR_RESULTS)) {
1632
- throw new Error(`Native vector query: limit must be an integer between 1 and ${MAX_CONVEX_VECTOR_RESULTS}`);
1633
- }
1634
- const results = await ctx.vectorSearch(asTableName(config.tableName), config.vectorIndexName, {
1635
- vector: args.vector,
1636
- ...limit !== void 0 ? { limit } : {},
1637
- ...filter ? { filter: (q) => buildVectorFilter(q, filter) } : {}
1638
- });
1639
- return results.map((result) => ({
1640
- id: String(result._id),
1641
- score: result._score
1642
- }));
1643
- }
1644
- });
1645
- var mastraNativeVectorQuery = server.queryGeneric({
1646
- args: {
1647
- op: values.v.union(values.v.literal("getByConvexIds"), values.v.literal("describe"), values.v.literal("listByIds")),
1648
- config: nativeVectorIndexConfigValidator,
1649
- ids: values.v.optional(values.v.array(values.v.string())),
1650
- includeVector: values.v.optional(values.v.boolean()),
1651
- countLimit: values.v.optional(values.v.number())
1652
- },
1653
- handler: async (ctx, args) => {
1654
- const config = args.config;
1655
- switch (args.op) {
1656
- case "getByConvexIds": {
1657
- const ids = args.ids;
1658
- if (!ids) {
1659
- throw new Error("Native vector query: ids are required");
1660
- }
1661
- const includeVector = args.includeVector === true;
1662
- const docs = await mapInBatches2(ids, (id) => ctx.db.get(asConvexId(id)));
1663
- return docs.filter((doc) => Boolean(doc)).map((doc) => includeVector ? doc : omitVectorField(doc, config));
1664
- }
1665
- case "describe": {
1666
- const limit = Math.max(1, Math.min(args.countLimit ?? 1e4, 1e4));
1667
- const docs = await ctx.db.query(asTableName(config.tableName)).take(limit + 1);
1668
- return {
1669
- count: Math.min(docs.length, limit),
1670
- countIsLimited: docs.length > limit
1671
- };
1672
- }
1673
- case "listByIds": {
1674
- const ids = args.ids;
1675
- if (!ids) {
1676
- throw new Error("Native vector query: ids are required");
1677
- }
1678
- return mapInBatches2(ids, (id) => findByRecordId(ctx, config, id));
1679
- }
1680
- default:
1681
- throw new Error(`Unsupported native vector query operation: ${args.op}`);
1682
- }
1683
- }
1684
- });
1685
- var mastraNativeVectorMutation = server.mutationGeneric({
1686
- args: {
1687
- op: values.v.union(values.v.literal("upsert"), values.v.literal("updateById"), values.v.literal("deleteByIds")),
1688
- config: nativeVectorIndexConfigValidator,
1689
- ids: values.v.optional(values.v.array(values.v.string())),
1690
- vectors: values.v.optional(values.v.array(values.v.array(values.v.number()))),
1691
- metadata: values.v.optional(values.v.any()),
1692
- id: values.v.optional(values.v.string()),
1693
- vector: values.v.optional(values.v.array(values.v.number()))
1694
- },
1695
- handler: async (ctx, args) => {
1696
- const config = args.config;
1697
- switch (args.op) {
1698
- case "upsert": {
1699
- const ids = args.ids;
1700
- const vectors = args.vectors;
1701
- if (!ids || !vectors) {
1702
- throw new Error("Native vector upsert: ids and vectors are required");
1703
- }
1704
- if (vectors.length !== ids.length) {
1705
- throw new Error(
1706
- `Native vector upsert: vectors length (${vectors.length}) must match ids length (${ids.length})`
1707
- );
1708
- }
1709
- const metadata = validateMetadataArray(args.metadata, ids.length);
1710
- if (new Set(ids).size !== ids.length) {
1711
- throw new Error("Native vector upsert: ids must be unique");
1712
- }
1713
- await mapInBatches2(ids, async (id, index) => {
1714
- const record = buildRecord({
1715
- config,
1716
- id,
1717
- vector: vectors[index],
1718
- metadata: metadata?.[index]
1719
- });
1720
- const existing = await findByRecordId(ctx, config, id);
1721
- if (existing?._id) {
1722
- const { _id: _, _creationTime: __, ...patch } = record;
1723
- if (metadata?.[index] !== void 0) {
1724
- clearMissingFilterFields(patch, metadata[index], config.filterFields);
1725
- }
1726
- await ctx.db.patch(existing._id, patch);
1727
- } else {
1728
- await ctx.db.insert(asTableName(config.tableName), record);
1729
- }
1730
- });
1731
- return { ok: true };
1732
- }
1733
- case "updateById": {
1734
- if (!args.id) {
1735
- throw new Error("Native vector update: id is required");
1736
- }
1737
- const existing = await findByRecordId(ctx, config, args.id);
1738
- if (!existing?._id) return { ok: true };
1739
- const patch = {};
1740
- if (args.vector) patch[vectorField(config)] = args.vector;
1741
- const metadata = validateMetadataRecord(args.metadata);
1742
- if (metadata !== void 0) {
1743
- const existingMetadata = isMetadataRecord(existing[metadataField(config)]) ? existing[metadataField(config)] : {};
1744
- patch[metadataField(config)] = { ...existingMetadata, ...metadata };
1745
- Object.assign(patch, pickFilterFields(patch[metadataField(config)], config.filterFields));
1746
- }
1747
- if (Object.keys(patch).length > 0) {
1748
- await ctx.db.patch(existing._id, patch);
1749
- }
1750
- return { ok: true };
1751
- }
1752
- case "deleteByIds": {
1753
- const ids = args.ids;
1754
- if (!ids) {
1755
- throw new Error("Native vector deleteByIds: ids are required");
1756
- }
1757
- const docs = await mapInBatches2(ids, (id) => findByRecordId(ctx, config, id));
1758
- await mapInBatches2(
1759
- docs.filter((doc) => Boolean(doc?._id)),
1760
- (doc) => ctx.db.delete(doc._id)
1761
- );
1762
- return { ok: true };
1763
- }
1764
- default:
1765
- throw new Error(`Unsupported native vector mutation operation: ${args.op}`);
1766
- }
1767
- }
1768
- });
1769
-
1770
- exports.mastraCache = mastraCache;
1771
- exports.mastraNativeVectorAction = mastraNativeVectorAction;
1772
- exports.mastraNativeVectorMutation = mastraNativeVectorMutation;
1773
- exports.mastraNativeVectorQuery = mastraNativeVectorQuery;
1774
- exports.mastraStorage = mastraStorage;
1775
- //# sourceMappingURL=chunk-2KILIA33.cjs.map
1776
- //# sourceMappingURL=chunk-2KILIA33.cjs.map