@mastra/convex 1.1.0 → 1.2.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 (42) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +31 -13
  3. package/dist/{chunk-FZDLZ4S3.js → chunk-AJFME2ZF.js} +323 -16
  4. package/dist/chunk-AJFME2ZF.js.map +1 -0
  5. package/dist/{chunk-JPWDG4L3.js → chunk-MC75WADX.js} +79 -4
  6. package/dist/chunk-MC75WADX.js.map +1 -0
  7. package/dist/{chunk-EEELVBWO.cjs → chunk-ORSDZTO4.cjs} +322 -15
  8. package/dist/chunk-ORSDZTO4.cjs.map +1 -0
  9. package/dist/{chunk-CV23JOCS.cjs → chunk-SFRHJGSM.cjs} +102 -2
  10. package/dist/chunk-SFRHJGSM.cjs.map +1 -0
  11. package/dist/docs/SKILL.md +1 -1
  12. package/dist/docs/assets/SOURCE_MAP.json +63 -23
  13. package/dist/index.cjs +634 -69
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +574 -49
  16. package/dist/index.js.map +1 -1
  17. package/dist/schema.cjs +60 -20
  18. package/dist/schema.d.ts +192 -2
  19. package/dist/schema.d.ts.map +1 -1
  20. package/dist/schema.js +1 -1
  21. package/dist/server/index-map.d.ts.map +1 -1
  22. package/dist/server/index.cjs +63 -23
  23. package/dist/server/index.d.ts +1 -1
  24. package/dist/server/index.d.ts.map +1 -1
  25. package/dist/server/index.js +2 -2
  26. package/dist/server/storage.d.ts.map +1 -1
  27. package/dist/storage/db/index.d.ts +28 -1
  28. package/dist/storage/db/index.d.ts.map +1 -1
  29. package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
  30. package/dist/storage/domains/channels/index.d.ts +19 -0
  31. package/dist/storage/domains/channels/index.d.ts.map +1 -0
  32. package/dist/storage/domains/schedules/index.d.ts +19 -0
  33. package/dist/storage/domains/schedules/index.d.ts.map +1 -0
  34. package/dist/storage/index.d.ts +3 -1
  35. package/dist/storage/index.d.ts.map +1 -1
  36. package/dist/storage/types.d.ts +42 -0
  37. package/dist/storage/types.d.ts.map +1 -1
  38. package/package.json +4 -4
  39. package/dist/chunk-CV23JOCS.cjs.map +0 -1
  40. package/dist/chunk-EEELVBWO.cjs.map +0 -1
  41. package/dist/chunk-FZDLZ4S3.js.map +0 -1
  42. package/dist/chunk-JPWDG4L3.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # @mastra/convex
2
2
 
3
+ ## 1.2.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Convex can now persist channel installations and provider configuration. ([#16718](https://github.com/mastra-ai/mastra/pull/16718))
8
+
9
+ ```ts
10
+ import { ConvexStore } from '@mastra/convex';
11
+
12
+ const storage = new ConvexStore({
13
+ id: 'app-storage',
14
+ deploymentUrl: process.env.CONVEX_URL!,
15
+ adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
16
+ });
17
+
18
+ const channels = await storage.getStore('channels');
19
+
20
+ await channels?.saveInstallation({
21
+ id: 'slack-agent-1',
22
+ platform: 'slack',
23
+ agentId: 'agent-1',
24
+ status: 'active',
25
+ webhookId: 'webhook-1',
26
+ data: { teamId: 'T123', botUserId: 'U123' },
27
+ createdAt: new Date(),
28
+ updatedAt: new Date(),
29
+ });
30
+ ```
31
+
32
+ - Workflow schedules can now be stored in Convex. ([#16710](https://github.com/mastra-ai/mastra/pull/16710))
33
+
34
+ ```ts
35
+ import { ConvexStore } from '@mastra/convex';
36
+
37
+ const storage = new ConvexStore({
38
+ id: 'app-storage',
39
+ deploymentUrl: process.env.CONVEX_URL!,
40
+ adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
41
+ });
42
+
43
+ const schedules = await storage.getStore('schedules');
44
+
45
+ await schedules?.createSchedule({
46
+ id: 'daily-summary',
47
+ target: { type: 'workflow', workflowId: 'summary-workflow' },
48
+ cron: '0 9 * * *',
49
+ status: 'active',
50
+ nextFireAt: Date.now(),
51
+ createdAt: Date.now(),
52
+ updatedAt: Date.now(),
53
+ });
54
+ ```
55
+
56
+ ### Patch Changes
57
+
58
+ - Improved Convex background task reliability with safer lifecycle updates, faster filtering, and smoother upgrades from older storage rows. ([#16724](https://github.com/mastra-ai/mastra/pull/16724))
59
+
60
+ - Updated dependencies [[`0cbece9`](https://github.com/mastra-ai/mastra/commit/0cbece9d832cb134a74cdbf3682d390a058215a4), [`7dfe1bc`](https://github.com/mastra-ai/mastra/commit/7dfe1bcfe71d261a6fd6bbf29b1dec49d78fb98f), [`70cb714`](https://github.com/mastra-ai/mastra/commit/70cb7149c8f16f478e15b58498254a53181750a4), [`7f9da22`](https://github.com/mastra-ai/mastra/commit/7f9da22efd5aa595e138a31de55a5f0f2f28b33d)]:
61
+ - @mastra/core@1.37.0-alpha.6
62
+
3
63
  ## 1.1.0
4
64
 
5
65
  ### Minor Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Convex adapters for Mastra:
4
4
 
5
- - `ConvexStore` implements the Mastra storage contract (threads, messages, workflows, scores, resources).
5
+ - `ConvexStore` implements the Mastra storage contract (threads, messages, workflows, scores, resources, schedules, channels, background tasks).
6
6
  - `ConvexVector` stores embeddings inside Convex and performs development-scale cosine similarity search.
7
7
  - `ConvexNativeVector` uses Convex native vector search for production workloads.
8
8
  - `ConvexServerCache` stores Mastra server cache entries in Convex for durable stream replay and response caching.
@@ -28,6 +28,11 @@ import {
28
28
  mastraResourcesTable,
29
29
  mastraWorkflowSnapshotsTable,
30
30
  mastraScoresTable,
31
+ mastraSchedulesTable,
32
+ mastraScheduleTriggersTable,
33
+ mastraChannelInstallationsTable,
34
+ mastraChannelConfigTable,
35
+ mastraBackgroundTasksTable,
31
36
  mastraVectorIndexesTable,
32
37
  mastraVectorsTable,
33
38
  mastraCacheTable,
@@ -41,6 +46,11 @@ export default defineSchema({
41
46
  mastra_resources: mastraResourcesTable,
42
47
  mastra_workflow_snapshots: mastraWorkflowSnapshotsTable,
43
48
  mastra_scorers: mastraScoresTable,
49
+ mastra_schedules: mastraSchedulesTable,
50
+ mastra_schedule_triggers: mastraScheduleTriggersTable,
51
+ mastra_channel_installations: mastraChannelInstallationsTable,
52
+ mastra_channel_config: mastraChannelConfigTable,
53
+ mastra_background_tasks: mastraBackgroundTasksTable,
44
54
  mastra_vector_indexes: mastraVectorIndexesTable,
45
55
  mastra_vectors: mastraVectorsTable,
46
56
  mastra_cache: mastraCacheTable,
@@ -160,24 +170,32 @@ Native vector search uses Convex's schema-defined vector indexes and action-only
160
170
 
161
171
  This adapter uses **typed Convex tables** for each Mastra domain:
162
172
 
163
- | Domain | Convex Table | Purpose |
164
- | -------------- | --------------------------- | -------------------- |
165
- | Threads | `mastra_threads` | Conversation threads |
166
- | Messages | `mastra_messages` | Chat messages |
167
- | Resources | `mastra_resources` | User working memory |
168
- | Workflows | `mastra_workflow_snapshots` | Workflow state |
169
- | Scorers | `mastra_scorers` | Evaluation data |
170
- | Vector Indexes | `mastra_vector_indexes` | Index metadata |
171
- | Vectors | `mastra_vectors` | Embeddings |
172
- | Cache | `mastra_cache` | Cache metadata |
173
- | Cache Items | `mastra_cache_list_items` | Cache list entries |
174
- | Fallback | `mastra_documents` | Unknown tables |
173
+ | Domain | Convex Table | Purpose |
174
+ | ---------------- | ------------------------------------------------------- | -------------------------------- |
175
+ | Threads | `mastra_threads` | Conversation threads |
176
+ | Messages | `mastra_messages` | Chat messages |
177
+ | Resources | `mastra_resources` | User working memory |
178
+ | Workflows | `mastra_workflow_snapshots` | Workflow state |
179
+ | Scorers | `mastra_scorers` | Evaluation data |
180
+ | Schedules | `mastra_schedules` | Workflow schedules |
181
+ | Triggers | `mastra_schedule_triggers` | Schedule history |
182
+ | Channels | `mastra_channel_installations`, `mastra_channel_config` | Channel installations and config |
183
+ | Background Tasks | `mastra_background_tasks` | Background task state |
184
+ | Vector Indexes | `mastra_vector_indexes` | Index metadata |
185
+ | Vectors | `mastra_vectors` | Embeddings |
186
+ | Cache | `mastra_cache` | Cache metadata |
187
+ | Cache Items | `mastra_cache_list_items` | Cache list entries |
188
+ | Fallback | `mastra_documents` | Unknown tables |
175
189
 
176
190
  All typed tables include:
177
191
 
178
192
  - An `id` field for Mastra's record ID (distinct from Convex's auto-generated `_id`)
179
193
  - A `by_record_id` index for efficient lookups by Mastra ID
180
194
 
195
+ Schedule due reads and trigger-history reads use bounded Convex queries to avoid deployment read limits. When no explicit trigger-history limit is provided, the adapter returns the newest 100 rows. Schedule listing is capped at 8,000 rows per call. Schedule rows also store a normalized `workflow_id` alongside the serialized target so workflow filters can run inside Convex before the listing cap is applied.
196
+
197
+ Background task reads and updates also tolerate older rows that were written to the fallback `mastra_documents` table.
198
+
181
199
  ## Testing
182
200
 
183
201
  Set the following environment variables before running tests:
@@ -1,5 +1,5 @@
1
1
  import { mutationGeneric, actionGeneric, queryGeneric } from 'convex/server';
2
- import { TABLE_SCORERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_RESOURCES, TABLE_MESSAGES, TABLE_THREADS } from '@mastra/core/storage/constants';
2
+ import { TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEDULES, TABLE_SCORERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_RESOURCES, TABLE_MESSAGES, TABLE_THREADS } from '@mastra/core/storage/constants';
3
3
  import { v } from 'convex/values';
4
4
 
5
5
  // src/server/cache.ts
@@ -227,6 +227,41 @@ var TABLE_INDEX_MAP = {
227
227
  { name: "by_created", fields: ["createdAt"] },
228
228
  { name: "by_record_id", fields: ["id"] }
229
229
  ],
230
+ mastra_schedules: [
231
+ { name: "by_workflow_status", fields: ["workflow_id", "status"] },
232
+ { name: "by_workflow_id", fields: ["workflow_id"] },
233
+ { name: "by_owner", fields: ["owner_type", "owner_id"] },
234
+ { name: "by_owner_id", fields: ["owner_id"] },
235
+ { name: "by_status_next_fire_at", fields: ["status", "next_fire_at"] },
236
+ { name: "by_created", fields: ["created_at"] },
237
+ { name: "by_record_id", fields: ["id"] }
238
+ ],
239
+ mastra_schedule_triggers: [
240
+ { name: "by_schedule_actual", fields: ["schedule_id", "actual_fire_at"] },
241
+ { name: "by_parent_trigger", fields: ["parent_trigger_id"] },
242
+ { name: "by_record_id", fields: ["id"] }
243
+ ],
244
+ mastra_channel_installations: [
245
+ { name: "by_platform_agent", fields: ["platform", "agentId"] },
246
+ { name: "by_webhook", fields: ["webhookId"] },
247
+ { name: "by_platform", fields: ["platform"] },
248
+ { name: "by_record_id", fields: ["id"] }
249
+ ],
250
+ mastra_channel_config: [
251
+ { name: "by_platform", fields: ["platform"] },
252
+ { name: "by_record_id", fields: ["id"] }
253
+ ],
254
+ mastra_background_tasks: [
255
+ { name: "by_agent_status", fields: ["agent_id", "status"] },
256
+ { name: "by_status_created", fields: ["status", "createdAt"] },
257
+ { name: "by_run", fields: ["run_id"] },
258
+ { name: "by_tool_call", fields: ["tool_call_id"] },
259
+ { name: "by_thread", fields: ["thread_id"] },
260
+ { name: "by_resource", fields: ["resource_id"] },
261
+ { name: "by_tool", fields: ["tool_name"] },
262
+ { name: "by_created", fields: ["createdAt"] },
263
+ { name: "by_record_id", fields: ["id"] }
264
+ ],
230
265
  mastra_vector_indexes: [
231
266
  { name: "by_name", fields: ["indexName"] },
232
267
  { name: "by_record_id", fields: ["id"] }
@@ -263,7 +298,44 @@ function findBestIndex(convexTable, filters) {
263
298
  var TABLE_VECTOR_INDEXES = "mastra_vector_indexes";
264
299
  var VECTOR_TABLE_PREFIX = "mastra_vector_";
265
300
  var CONVEX_TABLE_WORKFLOW_SNAPSHOTS = "mastra_workflow_snapshots";
301
+ var CONVEX_TABLE_BACKGROUND_TASKS = "mastra_background_tasks";
302
+ var CONVEX_TABLE_DOCUMENTS = "mastra_documents";
266
303
  var STORAGE_MUTATION_BATCH_SIZE = 25;
304
+ var DEFAULT_SCHEDULE_QUERY_LIMIT = 100;
305
+ var BACKGROUND_TASK_FIELD_ALIASES = {
306
+ tool_call_id: "toolCallId",
307
+ toolCallId: "tool_call_id",
308
+ tool_name: "toolName",
309
+ toolName: "tool_name",
310
+ agent_id: "agentId",
311
+ agentId: "agent_id",
312
+ run_id: "runId",
313
+ runId: "run_id",
314
+ thread_id: "threadId",
315
+ threadId: "thread_id",
316
+ resource_id: "resourceId",
317
+ resourceId: "resource_id",
318
+ suspend_payload: "suspendPayload",
319
+ suspendPayload: "suspend_payload",
320
+ retry_count: "retryCount",
321
+ retryCount: "retry_count",
322
+ max_retries: "maxRetries",
323
+ maxRetries: "max_retries",
324
+ timeout_ms: "timeoutMs",
325
+ timeoutMs: "timeout_ms"
326
+ };
327
+ function normalizeScheduleQueryLimit(limit) {
328
+ if (limit == null || !Number.isFinite(limit)) return DEFAULT_SCHEDULE_QUERY_LIMIT;
329
+ return Math.max(0, Math.floor(limit));
330
+ }
331
+ function applyConvexEqualityFilters(query, filters, indexedFields = /* @__PURE__ */ new Set()) {
332
+ const remainingFilters = filters?.filter((filter) => !indexedFields.has(filter.field));
333
+ if (!remainingFilters?.length) return query;
334
+ return query.filter((q) => {
335
+ const predicates = remainingFilters.map((filter) => q.eq(q.field(filter.field), filter.value));
336
+ return predicates.length === 1 ? predicates[0] : q.and(...predicates);
337
+ });
338
+ }
267
339
  async function mapInBatches(inputs, batchSize, mapper) {
268
340
  const results = [];
269
341
  for (let index = 0; index < inputs.length; index += batchSize) {
@@ -278,6 +350,58 @@ async function findExistingDocsByIds(ids, findDoc) {
278
350
  const docs = await mapInBatches([...new Set(ids)], STORAGE_MUTATION_BATCH_SIZE, findDoc);
279
351
  return docs.filter((doc) => Boolean(doc));
280
352
  }
353
+ function isBackgroundTasksTable(convexTable, request) {
354
+ return convexTable === CONVEX_TABLE_BACKGROUND_TASKS && request.tableName === TABLE_BACKGROUND_TASKS;
355
+ }
356
+ function matchesFilters(record, filters) {
357
+ return filters.every((filter) => {
358
+ if (record[filter.field] === filter.value) return true;
359
+ const alternateField = BACKGROUND_TASK_FIELD_ALIASES[filter.field];
360
+ return alternateField ? record[alternateField] === filter.value : false;
361
+ });
362
+ }
363
+ function mergeLegacyRecord(record, patch) {
364
+ const merged = { ...record };
365
+ for (const [field, value] of Object.entries(patch)) {
366
+ const alternateField = BACKGROUND_TASK_FIELD_ALIASES[field];
367
+ if (alternateField) delete merged[alternateField];
368
+ merged[field] = value;
369
+ }
370
+ return merged;
371
+ }
372
+ function stripPatchKeys(record, keys) {
373
+ const stripped = { ...record };
374
+ for (const key of keys) delete stripped[key];
375
+ return stripped;
376
+ }
377
+ function dedupeByRecordId(records) {
378
+ const seen = /* @__PURE__ */ new Set();
379
+ return records.filter((record) => {
380
+ if (record?.id == null) return true;
381
+ const id = String(record.id);
382
+ if (seen.has(id)) return false;
383
+ seen.add(id);
384
+ return true;
385
+ });
386
+ }
387
+ function isMissingBackgroundTaskSchemaError(error) {
388
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
389
+ return message.includes(CONVEX_TABLE_BACKGROUND_TASKS) && (message.includes("does not exist") || message.includes("not found") || message.includes("not defined") || message.includes("no such"));
390
+ }
391
+ async function findGenericDocumentById(ctx, tableName, id) {
392
+ return await ctx.db.query(CONVEX_TABLE_DOCUMENTS).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(id))).unique();
393
+ }
394
+ async function findGenericDocumentsByTable(ctx, tableName, limit) {
395
+ return await ctx.db.query(CONVEX_TABLE_DOCUMENTS).withIndex("by_table", (q) => q.eq("table", tableName)).take(limit);
396
+ }
397
+ async function filterLegacyRecordsWithoutTypedCopy(ctx, convexTable, legacyRecords) {
398
+ const records = await mapInBatches(legacyRecords, STORAGE_MUTATION_BATCH_SIZE, async (record) => {
399
+ if (record.id == null) return record;
400
+ const typedDoc = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", String(record.id))).unique();
401
+ return typedDoc ? null : record;
402
+ });
403
+ return records.filter((record) => Boolean(record));
404
+ }
281
405
  function coalesceTypedRecordsForBatchInsert(records) {
282
406
  const recordsById = /* @__PURE__ */ new Map();
283
407
  for (const record of records) {
@@ -309,6 +433,16 @@ function resolveTable(tableName) {
309
433
  return { convexTable: CONVEX_TABLE_WORKFLOW_SNAPSHOTS, isTyped: true };
310
434
  case TABLE_SCORERS:
311
435
  return { convexTable: "mastra_scorers", isTyped: true };
436
+ case TABLE_SCHEDULES:
437
+ return { convexTable: "mastra_schedules", isTyped: true };
438
+ case TABLE_SCHEDULE_TRIGGERS:
439
+ return { convexTable: "mastra_schedule_triggers", isTyped: true };
440
+ case TABLE_CHANNEL_INSTALLATIONS:
441
+ return { convexTable: "mastra_channel_installations", isTyped: true };
442
+ case TABLE_CHANNEL_CONFIG:
443
+ return { convexTable: "mastra_channel_config", isTyped: true };
444
+ case TABLE_BACKGROUND_TASKS:
445
+ return { convexTable: CONVEX_TABLE_BACKGROUND_TASKS, isTyped: true };
312
446
  case TABLE_VECTOR_INDEXES:
313
447
  return { convexTable: "mastra_vector_indexes", isTyped: true };
314
448
  default:
@@ -325,6 +459,14 @@ var mastraStorage = mutationGeneric(async (ctx, request) => {
325
459
  return handleVectorOperation(ctx, request);
326
460
  }
327
461
  if (isTyped) {
462
+ if (isBackgroundTasksTable(convexTable, request)) {
463
+ try {
464
+ return await handleTypedOperation(ctx, convexTable, request);
465
+ } catch (error) {
466
+ if (!isMissingBackgroundTaskSchemaError(error)) throw error;
467
+ return handleGenericOperation(ctx, request);
468
+ }
469
+ }
328
470
  return handleTypedOperation(ctx, convexTable, request);
329
471
  }
330
472
  return handleGenericOperation(ctx, request);
@@ -338,6 +480,100 @@ var mastraStorage = mutationGeneric(async (ctx, request) => {
338
480
  });
339
481
  async function handleTypedOperation(ctx, convexTable, request) {
340
482
  switch (request.op) {
483
+ case "createSchedule": {
484
+ if (convexTable !== "mastra_schedules") {
485
+ throw new Error(`createSchedule is only supported for mastra_schedules`);
486
+ }
487
+ const record = request.record;
488
+ const id = record.id;
489
+ if (!id) {
490
+ throw new Error(`Schedule is missing an id`);
491
+ }
492
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
493
+ if (existing) {
494
+ throw new Error(`Schedule with id "${id}" already exists`);
495
+ }
496
+ await ctx.db.insert(convexTable, record);
497
+ return { ok: true };
498
+ }
499
+ case "recordScheduleTrigger": {
500
+ if (convexTable !== "mastra_schedule_triggers") {
501
+ throw new Error(`recordScheduleTrigger is only supported for mastra_schedule_triggers`);
502
+ }
503
+ const record = request.record;
504
+ const id = record.id;
505
+ if (!id) {
506
+ throw new Error(`Schedule trigger is missing an id`);
507
+ }
508
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique();
509
+ if (existing) {
510
+ throw new Error(`Schedule trigger with id "${id}" already exists`);
511
+ }
512
+ await ctx.db.insert(convexTable, record);
513
+ return { ok: true };
514
+ }
515
+ case "listDueSchedules": {
516
+ if (convexTable !== "mastra_schedules") {
517
+ throw new Error(`listDueSchedules is only supported for mastra_schedules`);
518
+ }
519
+ const query = ctx.db.query(convexTable).withIndex("by_status_next_fire_at", (q) => q.eq("status", "active").lte("next_fire_at", request.now));
520
+ const docs = await query.take(normalizeScheduleQueryLimit(request.limit));
521
+ return { ok: true, result: docs };
522
+ }
523
+ case "updateScheduleNextFire": {
524
+ if (convexTable !== "mastra_schedules") {
525
+ throw new Error(`updateScheduleNextFire is only supported for mastra_schedules`);
526
+ }
527
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
528
+ if (!existing || existing.status !== "active" || existing.next_fire_at !== request.expectedNextFireAt) {
529
+ return { ok: true, result: false };
530
+ }
531
+ await ctx.db.patch(existing._id, {
532
+ next_fire_at: request.newNextFireAt,
533
+ last_fire_at: request.lastFireAt,
534
+ last_run_id: request.lastRunId,
535
+ updated_at: Date.now()
536
+ });
537
+ return { ok: true, result: true };
538
+ }
539
+ case "updateSchedule": {
540
+ if (convexTable !== "mastra_schedules") {
541
+ throw new Error(`updateSchedule is only supported for mastra_schedules`);
542
+ }
543
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
544
+ if (!existing) {
545
+ throw new Error(`Schedule ${request.id} not found`);
546
+ }
547
+ await ctx.db.patch(existing._id, request.patch);
548
+ return { ok: true, result: { ...existing, ...request.patch } };
549
+ }
550
+ case "listScheduleTriggers": {
551
+ if (convexTable !== "mastra_schedule_triggers") {
552
+ throw new Error(`listScheduleTriggers is only supported for mastra_schedule_triggers`);
553
+ }
554
+ const query = ctx.db.query(convexTable).withIndex("by_schedule_actual", (q) => {
555
+ let builder = q.eq("schedule_id", request.scheduleId);
556
+ if (request.fromActualFireAt != null) {
557
+ builder = builder.gte("actual_fire_at", request.fromActualFireAt);
558
+ }
559
+ if (request.toActualFireAt != null) {
560
+ builder = builder.lt("actual_fire_at", request.toActualFireAt);
561
+ }
562
+ return builder;
563
+ }).order("desc");
564
+ const docs = await query.take(normalizeScheduleQueryLimit(request.limit));
565
+ return { ok: true, result: docs };
566
+ }
567
+ case "deleteScheduleTriggers": {
568
+ if (convexTable !== "mastra_schedule_triggers") {
569
+ throw new Error(`deleteScheduleTriggers is only supported for mastra_schedule_triggers`);
570
+ }
571
+ const docs = await ctx.db.query(convexTable).withIndex("by_schedule_actual", (q) => q.eq("schedule_id", request.scheduleId)).take(STORAGE_MUTATION_BATCH_SIZE + 1);
572
+ const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
573
+ const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
574
+ await deleteDocs(ctx, docsToDelete);
575
+ return { ok: true, hasMore };
576
+ }
341
577
  case "insert": {
342
578
  const record = request.record;
343
579
  const id = record.id;
@@ -367,10 +603,36 @@ async function handleTypedOperation(ctx, convexTable, request) {
367
603
  });
368
604
  return { ok: true };
369
605
  }
606
+ case "patch": {
607
+ const patchRecord = stripPatchKeys(request.record, ["id"]);
608
+ const existing = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", request.id)).unique();
609
+ if (!existing) {
610
+ if (isBackgroundTasksTable(convexTable, request)) {
611
+ const legacy = await findGenericDocumentById(ctx, request.tableName, request.id);
612
+ if (legacy) {
613
+ await ctx.db.patch(legacy._id, { record: mergeLegacyRecord(legacy.record, patchRecord) });
614
+ return { ok: true, result: true };
615
+ }
616
+ }
617
+ return { ok: true, result: false };
618
+ }
619
+ await ctx.db.patch(existing._id, patchRecord);
620
+ if (isBackgroundTasksTable(convexTable, request)) {
621
+ const legacy = await findGenericDocumentById(ctx, request.tableName, request.id);
622
+ if (legacy) {
623
+ await ctx.db.delete(legacy._id);
624
+ }
625
+ }
626
+ return { ok: true, result: true };
627
+ }
370
628
  case "load": {
371
629
  const keys = request.keys;
372
630
  if (keys.id) {
373
631
  const doc = await ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", keys.id)).unique();
632
+ if (!doc && isBackgroundTasksTable(convexTable, request)) {
633
+ const legacy = await findGenericDocumentById(ctx, request.tableName, String(keys.id));
634
+ return { ok: true, result: legacy?.record ?? null };
635
+ }
374
636
  return { ok: true, result: doc || null };
375
637
  }
376
638
  if (convexTable === CONVEX_TABLE_WORKFLOW_SNAPSHOTS && typeof keys.workflow_name === "string" && typeof keys.run_id === "string") {
@@ -383,34 +645,47 @@ async function handleTypedOperation(ctx, convexTable, request) {
383
645
  }
384
646
  case "queryTable": {
385
647
  const maxDocs = request.limit ? Math.min(request.limit * 2, 1e4) : 1e4;
386
- let docs;
648
+ let query;
649
+ let indexedFields = /* @__PURE__ */ new Set();
387
650
  if (request.indexHint) {
388
651
  const hint = request.indexHint;
389
652
  if (hint.index === "by_workflow") {
390
- docs = await ctx.db.query(convexTable).withIndex("by_workflow", (q) => q.eq("workflow_name", hint.workflowName)).take(maxDocs);
653
+ query = ctx.db.query(convexTable).withIndex("by_workflow", (q) => q.eq("workflow_name", hint.workflowName));
391
654
  } else if (hint.index === "by_workflow_run") {
392
- docs = await ctx.db.query(convexTable).withIndex("by_workflow_run", (q) => q.eq("workflow_name", hint.workflowName).eq("run_id", hint.runId)).take(maxDocs);
655
+ query = ctx.db.query(convexTable).withIndex(
656
+ "by_workflow_run",
657
+ (q) => q.eq("workflow_name", hint.workflowName).eq("run_id", hint.runId)
658
+ );
393
659
  } else {
394
- docs = await ctx.db.query(convexTable).take(maxDocs);
660
+ query = ctx.db.query(convexTable);
395
661
  }
396
662
  } else if (request.filters && request.filters.length > 0) {
397
663
  const match = findBestIndex(convexTable, request.filters);
398
664
  if (match) {
399
- docs = await ctx.db.query(convexTable).withIndex(match.indexName, (q) => {
665
+ query = ctx.db.query(convexTable).withIndex(match.indexName, (q) => {
400
666
  let builder = q;
401
667
  for (const filter of match.indexedFilters) {
402
668
  builder = builder.eq(filter.field, filter.value);
403
669
  }
404
670
  return builder;
405
- }).take(maxDocs);
671
+ });
672
+ indexedFields = new Set(match.indexedFilters.map((filter) => filter.field));
406
673
  } else {
407
- docs = await ctx.db.query(convexTable).take(maxDocs);
674
+ query = ctx.db.query(convexTable);
408
675
  }
409
676
  } else {
410
- docs = await ctx.db.query(convexTable).take(maxDocs);
677
+ query = ctx.db.query(convexTable);
411
678
  }
412
- if (request.filters && request.filters.length > 0) {
413
- docs = docs.filter((doc) => request.filters.every((filter) => doc[filter.field] === filter.value));
679
+ let docs = await applyConvexEqualityFilters(query, request.filters, indexedFields).take(maxDocs);
680
+ if (isBackgroundTasksTable(convexTable, request)) {
681
+ const legacyDocs = await findGenericDocumentsByTable(ctx, request.tableName, maxDocs);
682
+ let legacyRecords = legacyDocs.map((doc) => doc.record);
683
+ if (request.filters && request.filters.length > 0) {
684
+ legacyRecords = legacyRecords.filter((record) => matchesFilters(record, request.filters));
685
+ }
686
+ legacyRecords = await filterLegacyRecordsWithoutTypedCopy(ctx, convexTable, legacyRecords);
687
+ docs.push(...legacyRecords);
688
+ docs = dedupeByRecordId(docs);
414
689
  }
415
690
  if (request.limit) {
416
691
  docs = docs.slice(0, request.limit);
@@ -421,15 +696,27 @@ async function handleTypedOperation(ctx, convexTable, request) {
421
696
  case "dropTable": {
422
697
  const docs = await ctx.db.query(convexTable).take(STORAGE_MUTATION_BATCH_SIZE + 1);
423
698
  const hasMore = docs.length > STORAGE_MUTATION_BATCH_SIZE;
424
- const docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
699
+ let docsToDelete = hasMore ? docs.slice(0, STORAGE_MUTATION_BATCH_SIZE) : docs;
700
+ let legacyHasMore = false;
701
+ if (!hasMore && docsToDelete.length < STORAGE_MUTATION_BATCH_SIZE && isBackgroundTasksTable(convexTable, request)) {
702
+ const remainingBatchSize = STORAGE_MUTATION_BATCH_SIZE - docsToDelete.length;
703
+ const legacyDocs = await findGenericDocumentsByTable(ctx, request.tableName, remainingBatchSize + 1);
704
+ legacyHasMore = legacyDocs.length > remainingBatchSize;
705
+ docsToDelete = docsToDelete.concat(legacyHasMore ? legacyDocs.slice(0, remainingBatchSize) : legacyDocs);
706
+ }
425
707
  await deleteDocs(ctx, docsToDelete);
426
- return { ok: true, hasMore };
708
+ return { ok: true, hasMore: hasMore || legacyHasMore };
427
709
  }
428
710
  case "deleteMany": {
429
711
  const docsToDelete = await findExistingDocsByIds(
430
712
  request.ids,
431
713
  (id) => ctx.db.query(convexTable).withIndex("by_record_id", (q) => q.eq("id", id)).unique()
432
714
  );
715
+ if (isBackgroundTasksTable(convexTable, request)) {
716
+ docsToDelete.push(
717
+ ...await findExistingDocsByIds(request.ids, (id) => findGenericDocumentById(ctx, request.tableName, id))
718
+ );
719
+ }
433
720
  await deleteDocs(ctx, docsToDelete);
434
721
  return { ok: true };
435
722
  }
@@ -484,6 +771,15 @@ async function handleVectorOperation(ctx, request) {
484
771
  });
485
772
  return { ok: true };
486
773
  }
774
+ case "patch": {
775
+ const patchRecord = stripPatchKeys(request.record, ["id", "indexName"]);
776
+ const existing = await ctx.db.query(convexTable).withIndex("by_index_id", (q) => q.eq("indexName", indexName).eq("id", request.id)).unique();
777
+ if (!existing) {
778
+ return { ok: true, result: false };
779
+ }
780
+ await ctx.db.patch(existing._id, patchRecord);
781
+ return { ok: true, result: true };
782
+ }
487
783
  case "load": {
488
784
  const keys = request.keys;
489
785
  if (keys.id) {
@@ -565,6 +861,17 @@ async function handleGenericOperation(ctx, request) {
565
861
  });
566
862
  return { ok: true };
567
863
  }
864
+ case "patch": {
865
+ const patchRecord = stripPatchKeys(request.record, ["id"]);
866
+ const existing = await ctx.db.query(convexTable).withIndex("by_table_primary", (q) => q.eq("table", tableName).eq("primaryKey", String(request.id))).unique();
867
+ if (!existing) {
868
+ return { ok: true, result: false };
869
+ }
870
+ await ctx.db.patch(existing._id, {
871
+ record: tableName === TABLE_BACKGROUND_TASKS ? mergeLegacyRecord(existing.record, patchRecord) : { ...existing.record, ...patchRecord }
872
+ });
873
+ return { ok: true, result: true };
874
+ }
568
875
  case "load": {
569
876
  const keys = request.keys;
570
877
  if (keys.id) {
@@ -581,7 +888,7 @@ async function handleGenericOperation(ctx, request) {
581
888
  let records = docs.map((doc) => doc.record);
582
889
  if (request.filters && request.filters.length > 0) {
583
890
  records = records.filter(
584
- (record) => request.filters.every((filter) => record?.[filter.field] === filter.value)
891
+ (record) => tableName === TABLE_BACKGROUND_TASKS ? matchesFilters(record, request.filters) : request.filters.every((filter) => record?.[filter.field] === filter.value)
585
892
  );
586
893
  }
587
894
  if (request.limit) {
@@ -883,5 +1190,5 @@ var mastraNativeVectorMutation = mutationGeneric({
883
1190
  });
884
1191
 
885
1192
  export { mastraCache, mastraNativeVectorAction, mastraNativeVectorMutation, mastraNativeVectorQuery, mastraStorage };
886
- //# sourceMappingURL=chunk-FZDLZ4S3.js.map
887
- //# sourceMappingURL=chunk-FZDLZ4S3.js.map
1193
+ //# sourceMappingURL=chunk-AJFME2ZF.js.map
1194
+ //# sourceMappingURL=chunk-AJFME2ZF.js.map