@mastra/convex 1.0.11 → 1.1.0-alpha.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +76 -4
  3. package/dist/cache/client.d.ts +21 -0
  4. package/dist/cache/client.d.ts.map +1 -0
  5. package/dist/cache/index.d.ts +35 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/types.d.ts +46 -0
  8. package/dist/cache/types.d.ts.map +1 -0
  9. package/dist/{chunk-FTVDAP6U.cjs → chunk-CV23JOCS.cjs} +44 -2
  10. package/dist/chunk-CV23JOCS.cjs.map +1 -0
  11. package/dist/chunk-EEELVBWO.cjs +893 -0
  12. package/dist/chunk-EEELVBWO.cjs.map +1 -0
  13. package/dist/chunk-FZDLZ4S3.js +887 -0
  14. package/dist/chunk-FZDLZ4S3.js.map +1 -0
  15. package/dist/{chunk-G5FLGAPE.js → chunk-JPWDG4L3.js} +42 -3
  16. package/dist/chunk-JPWDG4L3.js.map +1 -0
  17. package/dist/docs/SKILL.md +1 -1
  18. package/dist/docs/assets/SOURCE_MAP.json +44 -15
  19. package/dist/docs/references/reference-storage-convex.md +74 -12
  20. package/dist/docs/references/reference-vectors-convex.md +129 -7
  21. package/dist/index.cjs +515 -36
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +471 -23
  26. package/dist/index.js.map +1 -1
  27. package/dist/schema.cjs +29 -17
  28. package/dist/schema.d.ts +76 -0
  29. package/dist/schema.d.ts.map +1 -1
  30. package/dist/schema.js +1 -1
  31. package/dist/server/cache.d.ts +5 -0
  32. package/dist/server/cache.d.ts.map +1 -0
  33. package/dist/server/index.cjs +44 -16
  34. package/dist/server/index.d.ts +3 -1
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +2 -2
  37. package/dist/server/native-vector.d.ts +17 -0
  38. package/dist/server/native-vector.d.ts.map +1 -0
  39. package/dist/storage/client.d.ts +5 -0
  40. package/dist/storage/client.d.ts.map +1 -1
  41. package/dist/vector/native.d.ts +111 -0
  42. package/dist/vector/native.d.ts.map +1 -0
  43. package/package.json +5 -5
  44. package/dist/chunk-C6QDNSBM.cjs +0 -425
  45. package/dist/chunk-C6QDNSBM.cjs.map +0 -1
  46. package/dist/chunk-FTVDAP6U.cjs.map +0 -1
  47. package/dist/chunk-G5FLGAPE.js.map +0 -1
  48. package/dist/chunk-NXNW2MK5.js +0 -423
  49. package/dist/chunk-NXNW2MK5.js.map +0 -1
@@ -0,0 +1,893 @@
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_vector_indexes: [
233
+ { name: "by_name", fields: ["indexName"] },
234
+ { name: "by_record_id", fields: ["id"] }
235
+ ]
236
+ };
237
+ function findBestIndex(convexTable, filters) {
238
+ const indexes = TABLE_INDEX_MAP[convexTable];
239
+ if (!indexes || filters.length === 0) return null;
240
+ const filtersByField = /* @__PURE__ */ new Map();
241
+ for (const f of filters) {
242
+ filtersByField.set(f.field, f);
243
+ }
244
+ let best = null;
245
+ for (const index of indexes) {
246
+ let prefixLength = 0;
247
+ const indexedFilters = [];
248
+ for (const field of index.fields) {
249
+ const filter = filtersByField.get(field);
250
+ if (filter) {
251
+ prefixLength++;
252
+ indexedFilters.push(filter);
253
+ } else {
254
+ break;
255
+ }
256
+ }
257
+ if (prefixLength > 0 && (!best || prefixLength > best.prefixLength)) {
258
+ best = { indexName: index.name, indexedFilters, prefixLength };
259
+ }
260
+ }
261
+ return best ? { indexName: best.indexName, indexedFilters: best.indexedFilters } : null;
262
+ }
263
+
264
+ // src/server/storage.ts
265
+ var TABLE_VECTOR_INDEXES = "mastra_vector_indexes";
266
+ var VECTOR_TABLE_PREFIX = "mastra_vector_";
267
+ var CONVEX_TABLE_WORKFLOW_SNAPSHOTS = "mastra_workflow_snapshots";
268
+ var STORAGE_MUTATION_BATCH_SIZE = 25;
269
+ async function mapInBatches(inputs, batchSize, mapper) {
270
+ const results = [];
271
+ for (let index = 0; index < inputs.length; index += batchSize) {
272
+ results.push(...await Promise.all(inputs.slice(index, index + batchSize).map(mapper)));
273
+ }
274
+ return results;
275
+ }
276
+ async function deleteDocs(ctx, docs) {
277
+ await mapInBatches(docs, STORAGE_MUTATION_BATCH_SIZE, (doc) => ctx.db.delete(doc._id));
278
+ }
279
+ async function findExistingDocsByIds(ids, findDoc) {
280
+ const docs = await mapInBatches([...new Set(ids)], STORAGE_MUTATION_BATCH_SIZE, findDoc);
281
+ return docs.filter((doc) => Boolean(doc));
282
+ }
283
+ function coalesceTypedRecordsForBatchInsert(records) {
284
+ const recordsById = /* @__PURE__ */ new Map();
285
+ for (const record of records) {
286
+ const id = record.id;
287
+ if (!id) continue;
288
+ const key = String(id);
289
+ recordsById.set(key, { ...recordsById.get(key) ?? {}, ...record });
290
+ }
291
+ return [...recordsById.values()];
292
+ }
293
+ function coalesceLastRecordById(records) {
294
+ const recordsById = /* @__PURE__ */ new Map();
295
+ for (const record of records) {
296
+ const id = record.id;
297
+ if (!id) continue;
298
+ recordsById.set(String(id), record);
299
+ }
300
+ return [...recordsById.values()];
301
+ }
302
+ function resolveTable(tableName) {
303
+ switch (tableName) {
304
+ case constants.TABLE_THREADS:
305
+ return { convexTable: "mastra_threads", isTyped: true };
306
+ case constants.TABLE_MESSAGES:
307
+ return { convexTable: "mastra_messages", isTyped: true };
308
+ case constants.TABLE_RESOURCES:
309
+ return { convexTable: "mastra_resources", isTyped: true };
310
+ case constants.TABLE_WORKFLOW_SNAPSHOT:
311
+ return { convexTable: CONVEX_TABLE_WORKFLOW_SNAPSHOTS, isTyped: true };
312
+ case constants.TABLE_SCORERS:
313
+ return { convexTable: "mastra_scorers", isTyped: true };
314
+ case TABLE_VECTOR_INDEXES:
315
+ return { convexTable: "mastra_vector_indexes", isTyped: true };
316
+ default:
317
+ if (tableName.startsWith(VECTOR_TABLE_PREFIX)) {
318
+ return { convexTable: "mastra_vectors", isTyped: true };
319
+ }
320
+ return { convexTable: "mastra_documents", isTyped: false };
321
+ }
322
+ }
323
+ var mastraStorage = server.mutationGeneric(async (ctx, request) => {
324
+ try {
325
+ const { convexTable, isTyped } = resolveTable(request.tableName);
326
+ if (request.tableName.startsWith(VECTOR_TABLE_PREFIX) && request.tableName !== TABLE_VECTOR_INDEXES) {
327
+ return handleVectorOperation(ctx, request);
328
+ }
329
+ if (isTyped) {
330
+ return handleTypedOperation(ctx, convexTable, request);
331
+ }
332
+ return handleGenericOperation(ctx, request);
333
+ } catch (error) {
334
+ const err = error;
335
+ return {
336
+ ok: false,
337
+ error: err.message
338
+ };
339
+ }
340
+ });
341
+ async function handleTypedOperation(ctx, convexTable, request) {
342
+ switch (request.op) {
343
+ case "insert": {
344
+ const record = request.record;
345
+ const id = record.id;
346
+ if (!id) {
347
+ throw new Error(`Record is missing an id`);
348
+ }
349
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
350
+ if (existing) {
351
+ const { id: _, ...updateData } = record;
352
+ await ctx.db.patch(existing._id, updateData);
353
+ } else {
354
+ await ctx.db.insert(convexTable, record);
355
+ }
356
+ return { ok: true };
357
+ }
358
+ case "batchInsert": {
359
+ const records = coalesceTypedRecordsForBatchInsert(request.records);
360
+ await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
361
+ const id = record.id;
362
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
363
+ if (existing) {
364
+ const { id: _, ...updateData } = record;
365
+ await ctx.db.patch(existing._id, updateData);
366
+ } else {
367
+ await ctx.db.insert(convexTable, record);
368
+ }
369
+ });
370
+ return { ok: true };
371
+ }
372
+ case "load": {
373
+ const keys = request.keys;
374
+ if (keys.id) {
375
+ const doc = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", keys.id)).unique();
376
+ return { ok: true, result: doc || null };
377
+ }
378
+ if (convexTable === CONVEX_TABLE_WORKFLOW_SNAPSHOTS && typeof keys.workflow_name === "string" && typeof keys.run_id === "string") {
379
+ 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();
380
+ return { ok: true, result: doc || null };
381
+ }
382
+ const docs = await ctx.db.query(convexTable).take(1e4);
383
+ const match = docs.find((doc) => Object.entries(keys).every(([key, value]) => doc[key] === value));
384
+ return { ok: true, result: match || null };
385
+ }
386
+ case "queryTable": {
387
+ const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
388
+ let docs;
389
+ if (request.indexHint) {
390
+ const hint = request.indexHint;
391
+ if (hint.index === "by_workflow") {
392
+ docs = await ctx.db.query(convexTable).withIndex("by_workflow", (q) => q.eq("workflow_name", hint.workflowName)).take(maxDocs);
393
+ } else if (hint.index === "by_workflow_run") {
394
+ docs = await ctx.db.query(convexTable).withIndex("by_workflow_run", (q) => q.eq("workflow_name", hint.workflowName).eq("run_id", hint.runId)).take(maxDocs);
395
+ } else {
396
+ docs = await ctx.db.query(convexTable).take(maxDocs);
397
+ }
398
+ } else if (request.filters && request.filters.length > 0) {
399
+ const match = findBestIndex(convexTable, request.filters);
400
+ if (match) {
401
+ docs = await ctx.db.query(convexTable).withIndex(match.indexName, (q) => {
402
+ let builder = q;
403
+ for (const filter of match.indexedFilters) {
404
+ builder = builder.eq(filter.field, filter.value);
405
+ }
406
+ return builder;
407
+ }).take(maxDocs);
408
+ } else {
409
+ docs = await ctx.db.query(convexTable).take(maxDocs);
410
+ }
411
+ } else {
412
+ docs = await ctx.db.query(convexTable).take(maxDocs);
413
+ }
414
+ if (request.filters && request.filters.length > 0) {
415
+ docs = docs.filter((doc) => request.filters.every((filter) => doc[filter.field] === filter.value));
416
+ }
417
+ if (request.limit) {
418
+ docs = docs.slice(0, request.limit);
419
+ }
420
+ return { ok: true, result: docs };
421
+ }
422
+ case "clearTable":
423
+ case "dropTable": {
424
+ const docs = await ctx.db.query(convexTable).take(STORAGE_MUTATION_BATCH_SIZE + 1);
425
+ const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
426
+ const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
427
+ await deleteDocs(ctx, docsToDelete);
428
+ return { ok: true, hasMore };
429
+ }
430
+ case "deleteMany": {
431
+ const docsToDelete = await findExistingDocsByIds(
432
+ request.ids,
433
+ (id) => ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique()
434
+ );
435
+ await deleteDocs(ctx, docsToDelete);
436
+ return { ok: true };
437
+ }
438
+ default:
439
+ return { ok: false, error: `Unsupported operation ${request.op}` };
440
+ }
441
+ }
442
+ async function handleVectorOperation(ctx, request) {
443
+ const indexName = request.tableName.replace(VECTOR_TABLE_PREFIX, "");
444
+ const convexTable = "mastra_vectors";
445
+ switch (request.op) {
446
+ case "insert": {
447
+ const record = request.record;
448
+ const id = record.id;
449
+ if (!id) {
450
+ throw new Error(`Vector record is missing an id`);
451
+ }
452
+ const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique();
453
+ if (existing) {
454
+ await ctx.db.patch(existing._id, {
455
+ embedding: record.embedding,
456
+ metadata: record.metadata
457
+ });
458
+ } else {
459
+ await ctx.db.insert(convexTable, {
460
+ id,
461
+ indexName,
462
+ embedding: record.embedding,
463
+ metadata: record.metadata
464
+ });
465
+ }
466
+ return { ok: true };
467
+ }
468
+ case "batchInsert": {
469
+ const records = coalesceLastRecordById(request.records);
470
+ await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
471
+ const id = record.id;
472
+ const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique();
473
+ if (existing) {
474
+ await ctx.db.patch(existing._id, {
475
+ embedding: record.embedding,
476
+ metadata: record.metadata
477
+ });
478
+ } else {
479
+ await ctx.db.insert(convexTable, {
480
+ id,
481
+ indexName,
482
+ embedding: record.embedding,
483
+ metadata: record.metadata
484
+ });
485
+ }
486
+ });
487
+ return { ok: true };
488
+ }
489
+ case "load": {
490
+ const keys = request.keys;
491
+ if (keys.id) {
492
+ const doc = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", keys.id)).unique();
493
+ return { ok: true, result: doc || null };
494
+ }
495
+ return { ok: true, result: null };
496
+ }
497
+ case "queryTable": {
498
+ const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
499
+ let docs = await ctx.db.query(convexTable).withIndex("by_index", (q) => q.eq("indexName", indexName)).take(maxDocs);
500
+ if (request.filters && request.filters.length > 0) {
501
+ docs = docs.filter((doc) => request.filters.every((filter) => doc[filter.field] === filter.value));
502
+ }
503
+ if (request.limit) {
504
+ docs = docs.slice(0, request.limit);
505
+ }
506
+ return { ok: true, result: docs };
507
+ }
508
+ case "clearTable":
509
+ case "dropTable": {
510
+ const docs = await ctx.db.query(convexTable).withIndex("by_index", (q) => q.eq("indexName", indexName)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
511
+ const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
512
+ const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
513
+ await deleteDocs(ctx, docsToDelete);
514
+ return { ok: true, hasMore };
515
+ }
516
+ case "deleteMany": {
517
+ const docsToDelete = await findExistingDocsByIds(
518
+ request.ids,
519
+ (id) => (
520
+ // Use composite key (indexName, id) to scope deletion per index
521
+ ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", id)).unique()
522
+ )
523
+ );
524
+ await deleteDocs(ctx, docsToDelete);
525
+ return { ok: true };
526
+ }
527
+ default:
528
+ return { ok: false, error: `Unsupported operation ${request.op}` };
529
+ }
530
+ }
531
+ async function handleGenericOperation(ctx, request) {
532
+ const tableName = request.tableName;
533
+ const convexTable = "mastra_documents";
534
+ switch (request.op) {
535
+ case "insert": {
536
+ const record = request.record;
537
+ if (!record.id) {
538
+ throw new Error(`Record for table ${tableName} is missing an id`);
539
+ }
540
+ const primaryKey = String(record.id);
541
+ const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", primaryKey)).unique();
542
+ if (existing) {
543
+ await ctx.db.patch(existing._id, { record });
544
+ } else {
545
+ await ctx.db.insert(convexTable, {
546
+ table: tableName,
547
+ primaryKey,
548
+ record
549
+ });
550
+ }
551
+ return { ok: true };
552
+ }
553
+ case "batchInsert": {
554
+ const records = coalesceLastRecordById(request.records);
555
+ await mapInBatches(records, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
556
+ const primaryKey = String(record.id);
557
+ const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", primaryKey)).unique();
558
+ if (existing) {
559
+ await ctx.db.patch(existing._id, { record });
560
+ } else {
561
+ await ctx.db.insert(convexTable, {
562
+ table: tableName,
563
+ primaryKey,
564
+ record
565
+ });
566
+ }
567
+ });
568
+ return { ok: true };
569
+ }
570
+ case "load": {
571
+ const keys = request.keys;
572
+ if (keys.id) {
573
+ const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(keys.id))).unique();
574
+ return { ok: true, result: existing ? existing.record : null };
575
+ }
576
+ const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(1e4);
577
+ const match = docs.find((doc) => Object.entries(keys).every(([key, value]) => doc.record?.[key] === value));
578
+ return { ok: true, result: match ? match.record : null };
579
+ }
580
+ case "queryTable": {
581
+ const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
582
+ const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(maxDocs);
583
+ let records = docs.map((doc) => doc.record);
584
+ if (request.filters && request.filters.length > 0) {
585
+ records = records.filter(
586
+ (record) => request.filters.every((filter) => record?.[filter.field] === filter.value)
587
+ );
588
+ }
589
+ if (request.limit) {
590
+ records = records.slice(0, request.limit);
591
+ }
592
+ return { ok: true, result: records };
593
+ }
594
+ case "clearTable":
595
+ case "dropTable": {
596
+ const docs = await ctx.db.query(convexTable).withIndex("by_table", (q) => q.eq("table", tableName)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
597
+ const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
598
+ const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
599
+ await deleteDocs(ctx, docsToDelete);
600
+ return { ok: true, hasMore };
601
+ }
602
+ case "deleteMany": {
603
+ const docsToDelete = await findExistingDocsByIds(
604
+ request.ids,
605
+ (id) => ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(id))).unique()
606
+ );
607
+ await deleteDocs(ctx, docsToDelete);
608
+ return { ok: true };
609
+ }
610
+ default:
611
+ return { ok: false, error: `Unsupported operation ${request.op}` };
612
+ }
613
+ }
614
+ var DEFAULT_ID_FIELD = "id";
615
+ var DEFAULT_ID_INDEX = "by_record_id";
616
+ var DEFAULT_VECTOR_FIELD = "embedding";
617
+ var DEFAULT_METADATA_FIELD = "metadata";
618
+ var NATIVE_VECTOR_BATCH_SIZE = 25;
619
+ var MAX_CONVEX_VECTOR_RESULTS = 256;
620
+ var nativeVectorFilterValueValidator = values.v.union(values.v.string(), values.v.number(), values.v.boolean(), values.v.null());
621
+ var nativeVectorFilterClauseValidator = values.v.object({
622
+ field: values.v.string(),
623
+ value: nativeVectorFilterValueValidator
624
+ });
625
+ var nativeVectorFilterValidator = values.v.union(
626
+ nativeVectorFilterClauseValidator,
627
+ values.v.object({ $or: values.v.array(nativeVectorFilterClauseValidator) })
628
+ );
629
+ var nativeVectorIndexConfigValidator = values.v.object({
630
+ tableName: values.v.string(),
631
+ vectorIndexName: values.v.string(),
632
+ dimension: values.v.optional(values.v.number()),
633
+ idField: values.v.optional(values.v.string()),
634
+ idIndexName: values.v.optional(values.v.string()),
635
+ vectorField: values.v.optional(values.v.string()),
636
+ metadataField: values.v.optional(values.v.string()),
637
+ filterFields: values.v.optional(values.v.array(values.v.string()))
638
+ });
639
+ function idField(config) {
640
+ return config.idField ?? DEFAULT_ID_FIELD;
641
+ }
642
+ function idIndexName(config) {
643
+ return config.idIndexName ?? DEFAULT_ID_INDEX;
644
+ }
645
+ function vectorField(config) {
646
+ return config.vectorField ?? DEFAULT_VECTOR_FIELD;
647
+ }
648
+ function metadataField(config) {
649
+ return config.metadataField ?? DEFAULT_METADATA_FIELD;
650
+ }
651
+ function asTableName(tableName) {
652
+ return tableName;
653
+ }
654
+ function asConvexId(id) {
655
+ return id;
656
+ }
657
+ function pickFilterFields(metadata, filterFields) {
658
+ const fields = {};
659
+ if (!metadata || !filterFields) return fields;
660
+ for (const field of filterFields) {
661
+ const value = metadata[field];
662
+ if (value !== void 0) {
663
+ fields[field] = value;
664
+ }
665
+ }
666
+ return fields;
667
+ }
668
+ function isMetadataRecord(value) {
669
+ return typeof value === "object" && value !== null && !Array.isArray(value);
670
+ }
671
+ function validateMetadataArray(metadata, idsLength) {
672
+ if (metadata === void 0) return void 0;
673
+ if (!Array.isArray(metadata)) {
674
+ throw new Error("Native vector upsert: metadata must be an array matching ids when provided");
675
+ }
676
+ if (metadata.length !== idsLength) {
677
+ throw new Error(`Native vector upsert: metadata length (${metadata.length}) must match ids length (${idsLength})`);
678
+ }
679
+ if (!metadata.every(isMetadataRecord)) {
680
+ throw new Error("Native vector upsert: metadata entries must be objects when provided");
681
+ }
682
+ return metadata;
683
+ }
684
+ function validateMetadataRecord(metadata) {
685
+ if (metadata === void 0) return void 0;
686
+ if (!isMetadataRecord(metadata)) {
687
+ throw new Error("Native vector update: metadata must be an object when provided");
688
+ }
689
+ return metadata;
690
+ }
691
+ function clearMissingFilterFields(patch, metadata, filterFields) {
692
+ if (!filterFields) return;
693
+ for (const field of filterFields) {
694
+ if (metadata[field] === void 0) {
695
+ patch[field] = void 0;
696
+ }
697
+ }
698
+ }
699
+ function omitVectorField(doc, config) {
700
+ const { [vectorField(config)]: _, ...docWithoutVector } = doc;
701
+ return docWithoutVector;
702
+ }
703
+ function buildRecord({
704
+ config,
705
+ id,
706
+ vector,
707
+ metadata
708
+ }) {
709
+ return {
710
+ [idField(config)]: id,
711
+ [vectorField(config)]: vector,
712
+ ...metadata !== void 0 ? { [metadataField(config)]: metadata } : {},
713
+ ...pickFilterFields(metadata, config.filterFields)
714
+ };
715
+ }
716
+ async function findByRecordId(ctx, config, id) {
717
+ return ctx.db.query(asTableName(config.tableName)).withIndex(idIndexName(config), (q) => q.eq(idField(config), id)).unique();
718
+ }
719
+ async function mapInBatches2(inputs, mapper) {
720
+ const results = [];
721
+ for (let index = 0; index < inputs.length; index += NATIVE_VECTOR_BATCH_SIZE) {
722
+ results.push(
723
+ ...await Promise.all(
724
+ inputs.slice(index, index + NATIVE_VECTOR_BATCH_SIZE).map((input, batchIndex) => mapper(input, index + batchIndex))
725
+ )
726
+ );
727
+ }
728
+ return results;
729
+ }
730
+ function buildVectorFilter(q, filter) {
731
+ if (!filter) return void 0;
732
+ if ("$or" in filter) {
733
+ return q.or(...filter.$or.map((clause) => q.eq(clause.field, clause.value)));
734
+ }
735
+ return q.eq(filter.field, filter.value);
736
+ }
737
+ var mastraNativeVectorAction = server.actionGeneric({
738
+ args: {
739
+ config: nativeVectorIndexConfigValidator,
740
+ vector: values.v.array(values.v.number()),
741
+ limit: values.v.optional(values.v.number()),
742
+ filter: values.v.optional(nativeVectorFilterValidator)
743
+ },
744
+ handler: async (ctx, args) => {
745
+ const config = args.config;
746
+ const limit = args.limit;
747
+ const filter = args.filter;
748
+ if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1 || limit > MAX_CONVEX_VECTOR_RESULTS)) {
749
+ throw new Error(`Native vector query: limit must be an integer between 1 and ${MAX_CONVEX_VECTOR_RESULTS}`);
750
+ }
751
+ const results = await ctx.vectorSearch(asTableName(config.tableName), config.vectorIndexName, {
752
+ vector: args.vector,
753
+ ...limit !== void 0 ? { limit } : {},
754
+ ...filter ? { filter: (q) => buildVectorFilter(q, filter) } : {}
755
+ });
756
+ return results.map((result) => ({
757
+ id: String(result._id),
758
+ score: result._score
759
+ }));
760
+ }
761
+ });
762
+ var mastraNativeVectorQuery = server.queryGeneric({
763
+ args: {
764
+ op: values.v.union(values.v.literal("getByConvexIds"), values.v.literal("describe"), values.v.literal("listByIds")),
765
+ config: nativeVectorIndexConfigValidator,
766
+ ids: values.v.optional(values.v.array(values.v.string())),
767
+ includeVector: values.v.optional(values.v.boolean()),
768
+ countLimit: values.v.optional(values.v.number())
769
+ },
770
+ handler: async (ctx, args) => {
771
+ const config = args.config;
772
+ switch (args.op) {
773
+ case "getByConvexIds": {
774
+ const ids = args.ids;
775
+ if (!ids) {
776
+ throw new Error("Native vector query: ids are required");
777
+ }
778
+ const includeVector = args.includeVector === true;
779
+ const docs = await mapInBatches2(ids, (id) => ctx.db.get(asConvexId(id)));
780
+ return docs.filter((doc) => Boolean(doc)).map((doc) => includeVector ? doc : omitVectorField(doc, config));
781
+ }
782
+ case "describe": {
783
+ const limit = Math.max(1, Math.min(args.countLimit ?? 1e4, 1e4));
784
+ const docs = await ctx.db.query(asTableName(config.tableName)).take(limit + 1);
785
+ return {
786
+ count: Math.min(docs.length, limit),
787
+ countIsLimited: docs.length > limit
788
+ };
789
+ }
790
+ case "listByIds": {
791
+ const ids = args.ids;
792
+ if (!ids) {
793
+ throw new Error("Native vector query: ids are required");
794
+ }
795
+ return mapInBatches2(ids, (id) => findByRecordId(ctx, config, id));
796
+ }
797
+ default:
798
+ throw new Error(`Unsupported native vector query operation: ${args.op}`);
799
+ }
800
+ }
801
+ });
802
+ var mastraNativeVectorMutation = server.mutationGeneric({
803
+ args: {
804
+ op: values.v.union(values.v.literal("upsert"), values.v.literal("updateById"), values.v.literal("deleteByIds")),
805
+ config: nativeVectorIndexConfigValidator,
806
+ ids: values.v.optional(values.v.array(values.v.string())),
807
+ vectors: values.v.optional(values.v.array(values.v.array(values.v.number()))),
808
+ metadata: values.v.optional(values.v.any()),
809
+ id: values.v.optional(values.v.string()),
810
+ vector: values.v.optional(values.v.array(values.v.number()))
811
+ },
812
+ handler: async (ctx, args) => {
813
+ const config = args.config;
814
+ switch (args.op) {
815
+ case "upsert": {
816
+ const ids = args.ids;
817
+ const vectors = args.vectors;
818
+ if (!ids || !vectors) {
819
+ throw new Error("Native vector upsert: ids and vectors are required");
820
+ }
821
+ if (vectors.length !== ids.length) {
822
+ throw new Error(
823
+ `Native vector upsert: vectors length (${vectors.length}) must match ids length (${ids.length})`
824
+ );
825
+ }
826
+ const metadata = validateMetadataArray(args.metadata, ids.length);
827
+ if (new Set(ids).size !== ids.length) {
828
+ throw new Error("Native vector upsert: ids must be unique");
829
+ }
830
+ await mapInBatches2(ids, async (id, index) => {
831
+ const record = buildRecord({
832
+ config,
833
+ id,
834
+ vector: vectors[index],
835
+ metadata: metadata?.[index]
836
+ });
837
+ const existing = await findByRecordId(ctx, config, id);
838
+ if (existing?._id) {
839
+ const { _id: _, _creationTime: __, ...patch } = record;
840
+ if (metadata?.[index] !== void 0) {
841
+ clearMissingFilterFields(patch, metadata[index], config.filterFields);
842
+ }
843
+ await ctx.db.patch(existing._id, patch);
844
+ } else {
845
+ await ctx.db.insert(asTableName(config.tableName), record);
846
+ }
847
+ });
848
+ return { ok: true };
849
+ }
850
+ case "updateById": {
851
+ if (!args.id) {
852
+ throw new Error("Native vector update: id is required");
853
+ }
854
+ const existing = await findByRecordId(ctx, config, args.id);
855
+ if (!existing?._id) return { ok: true };
856
+ const patch = {};
857
+ if (args.vector) patch[vectorField(config)] = args.vector;
858
+ const metadata = validateMetadataRecord(args.metadata);
859
+ if (metadata !== void 0) {
860
+ const existingMetadata = isMetadataRecord(existing[metadataField(config)]) ? existing[metadataField(config)] : {};
861
+ patch[metadataField(config)] = { ...existingMetadata, ...metadata };
862
+ Object.assign(patch, pickFilterFields(patch[metadataField(config)], config.filterFields));
863
+ }
864
+ if (Object.keys(patch).length > 0) {
865
+ await ctx.db.patch(existing._id, patch);
866
+ }
867
+ return { ok: true };
868
+ }
869
+ case "deleteByIds": {
870
+ const ids = args.ids;
871
+ if (!ids) {
872
+ throw new Error("Native vector deleteByIds: ids are required");
873
+ }
874
+ const docs = await mapInBatches2(ids, (id) => findByRecordId(ctx, config, id));
875
+ await mapInBatches2(
876
+ docs.filter((doc) => Boolean(doc?._id)),
877
+ (doc) => ctx.db.delete(doc._id)
878
+ );
879
+ return { ok: true };
880
+ }
881
+ default:
882
+ throw new Error(`Unsupported native vector mutation operation: ${args.op}`);
883
+ }
884
+ }
885
+ });
886
+
887
+ exports.mastraCache = mastraCache;
888
+ exports.mastraNativeVectorAction = mastraNativeVectorAction;
889
+ exports.mastraNativeVectorMutation = mastraNativeVectorMutation;
890
+ exports.mastraNativeVectorQuery = mastraNativeVectorQuery;
891
+ exports.mastraStorage = mastraStorage;
892
+ //# sourceMappingURL=chunk-EEELVBWO.cjs.map
893
+ //# sourceMappingURL=chunk-EEELVBWO.cjs.map