@mastra/mysql 0.8.0 → 0.8.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # @mastra/mysql
2
2
 
3
+ ## 0.8.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Added HTTP endpoints for caller-driven experiments: `POST /datasets/:datasetId/experiments` now accepts `start: false` to create an experiment without spawning the runner (with an optional target and run-level `scorerIds`), and new routes `POST /datasets/:datasetId/experiments/:experimentId/items/:itemId/run`, `POST /datasets/:datasetId/experiments/:experimentId/results`, and `POST /datasets/:datasetId/experiments/:experimentId/finalize` let external orchestrators run one item server-side, submit externally computed per-item results (idempotent upsert on retries), and finalize the run with server-computed counts. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
8
+
9
+ - Added `createDatasetExperiment()`, `runExperimentItem()`, `submitExperimentResult()`, and `finalizeExperiment()` methods so a caller-owned orchestrator (for example Temporal) can drive an experiment loop while Mastra either executes each item server-side or ingests externally computed results. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
10
+
11
+ - Added `upsertExperimentResult()` to the experiments storage domain plus an `attempt` column on experiment results and a nullable target with an optional `scorerIds` column on experiments, enabling retry-safe result writes for caller-driven experiments (retried submissions with the same `(experimentId, itemId, attempt)` key converge on a single row). `saveScore()` now accepts an optional caller-supplied `id` and upserts on it, so retried experiment submissions replace their previous score rows (latest wins) instead of accumulating duplicates. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
12
+
13
+ - Added caller-driven experiments so an external orchestrator (for example Temporal workers) can own the experiment loop while Mastra stays the system of record. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
14
+
15
+ Create an experiment with `dataset.createExperiment()` (idempotent when you pass your own id). With a target, Mastra runs each item for you: call `dataset.runExperimentItem()` per item and Mastra executes the registered agent or workflow, resolves scorers (experiment `scorers`, falling back to item `scorerIds`, then dataset `scorerIds`), and upserts the result. Without a target, run everything yourself and report per-item results with `dataset.submitExperimentResult()` (upsert semantics on `(experimentId, itemId, attempt)` so retried workers converge on a single row). Either way, close the run with `dataset.finalizeExperiment()` and Mastra computes per-item succeeded/failed/skipped counts from the persisted rows. Results go into the same storage as native runs, so Studio views, comparisons, and review summaries work unchanged.
16
+
17
+ ```typescript
18
+ // Caller drives the loop, Mastra runs each item
19
+ const { experimentId } = await dataset.createExperiment({
20
+ id: workflowRunId,
21
+ targetType: 'agent',
22
+ targetId: 'support-agent',
23
+ scorers: ['accuracy'],
24
+ });
25
+
26
+ await dataset.runExperimentItem({ experimentId, itemId });
27
+
28
+ // Or: caller runs everything, Mastra ingests results
29
+ const ingest = await dataset.createExperiment({ id: workflowRunId });
30
+ await dataset.submitExperimentResult({
31
+ experimentId: ingest.experimentId,
32
+ itemId,
33
+ output,
34
+ scores: [{ scorerId: 'accuracy', score: 0.92 }],
35
+ });
36
+
37
+ const experiment = await dataset.finalizeExperiment({ experimentId });
38
+ ```
39
+
40
+ - Fixed versioned dataset item lookups to return the item visible in the requested dataset snapshot. ([#21979](https://github.com/mastra-ai/mastra/pull/21979))
41
+
42
+ - Updated dependencies [[`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`acc3471`](https://github.com/mastra-ai/mastra/commit/acc3471de5f3fde8027ee4e355af292b2bc1bc30), [`b6a771e`](https://github.com/mastra-ai/mastra/commit/b6a771ef23d203ddb348efca8065eff65def8191), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`26d4016`](https://github.com/mastra-ai/mastra/commit/26d40160ff7f7d8bf95fee2039a52cbc83863533), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`57c5103`](https://github.com/mastra-ai/mastra/commit/57c51035a2a36e3df3c4f32f46bb789a66ed5946)]:
43
+ - @mastra/core@1.61.0-alpha.3
44
+
45
+ ## 0.8.1-alpha.0
46
+
47
+ ### Patch Changes
48
+
49
+ - Fixed concurrent resume() calls on the same suspended workflow run executing downstream steps more than once. A resume now atomically claims the run before executing anything, so only one caller continues a given suspension. Losing callers throw WORKFLOW_RESUME_ALREADY_CLAIMED without running any steps. Fixes #20443 ([#21725](https://github.com/mastra-ai/mastra/pull/21725))
50
+
51
+ - Workflow state updates now support an optional expectedStatus guard, so a status change is only applied when the stored run is in an expected state. This is what makes concurrent workflow resumes safe. ([#21725](https://github.com/mastra-ai/mastra/pull/21725))
52
+
53
+ - Resume conflicts now return 409 Conflict. When a suspended workflow run has already been resumed by another caller, the resume endpoints respond with 409 instead of a generic error. ([#21725](https://github.com/mastra-ai/mastra/pull/21725))
54
+
55
+ - Updated dependencies [[`88d14ca`](https://github.com/mastra-ai/mastra/commit/88d14cac008582a618fecc3d5c7fd3bdf4f6ddc3), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`84a5b69`](https://github.com/mastra-ai/mastra/commit/84a5b699f84d6bae0a34efe5a970d891090b9f41), [`038b7b4`](https://github.com/mastra-ai/mastra/commit/038b7b405cb4ac25ab3f3031334111b1f87ac112), [`4132d61`](https://github.com/mastra-ai/mastra/commit/4132d61f8367077120ee9e6420d3224dffd93c93)]:
56
+ - @mastra/core@1.60.1-alpha.0
57
+
3
58
  ## 0.8.0
4
59
 
5
60
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -2577,7 +2577,11 @@ var DatasetsMySQL = class DatasetsMySQL extends _mastra_core_storage.DatasetsSto
2577
2577
  try {
2578
2578
  const tableItemsName = formatTableName(_mastra_core_storage.TABLE_DATASET_ITEMS);
2579
2579
  let rows;
2580
- if (args.datasetVersion !== void 0) [rows] = await this.pool.execute(`SELECT * FROM ${tableItemsName} WHERE \`id\` = ? AND \`datasetVersion\` = ? AND \`isDeleted\` = 0`, [args.id, args.datasetVersion]);
2580
+ if (args.datasetVersion !== void 0) [rows] = await this.pool.execute(`SELECT * FROM ${tableItemsName} WHERE \`id\` = ? AND \`datasetVersion\` <= ? AND (\`validTo\` IS NULL OR \`validTo\` > ?) AND \`isDeleted\` = 0 ORDER BY \`datasetVersion\` DESC LIMIT 1`, [
2581
+ args.id,
2582
+ args.datasetVersion,
2583
+ args.datasetVersion
2584
+ ]);
2581
2585
  else [rows] = await this.pool.execute(`SELECT * FROM ${tableItemsName} WHERE \`id\` = ? AND \`validTo\` IS NULL AND \`isDeleted\` = 0`, [args.id]);
2582
2586
  return rows.length > 0 ? this.mapItem(rows[0]) : null;
2583
2587
  } catch (error) {
@@ -3014,7 +3018,8 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3014
3018
  "experimentSetId",
3015
3019
  "comparisonId",
3016
3020
  "variantId",
3017
- "trialIndex"
3021
+ "trialIndex",
3022
+ "scorerIds"
3018
3023
  ]
3019
3024
  });
3020
3025
  await this.operations.alterTable({
@@ -3023,7 +3028,8 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3023
3028
  ifNotExists: [
3024
3029
  "comment",
3025
3030
  "organizationId",
3026
- "projectId"
3031
+ "projectId",
3032
+ "attempt"
3027
3033
  ]
3028
3034
  });
3029
3035
  await this.createDefaultIndexes();
@@ -3041,8 +3047,9 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3041
3047
  agentVersion: row.agentVersion ?? null,
3042
3048
  organizationId: row.organizationId ?? null,
3043
3049
  projectId: row.projectId ?? null,
3044
- targetType: row.targetType,
3045
- targetId: row.targetId,
3050
+ targetType: row.targetType ?? null,
3051
+ targetId: row.targetId ?? null,
3052
+ scorerIds: parseJSON$2(row.scorerIds) ?? null,
3046
3053
  name: row.name ?? void 0,
3047
3054
  description: row.description ?? void 0,
3048
3055
  metadata: parseJSON$2(row.metadata),
@@ -3078,6 +3085,7 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3078
3085
  startedAt: parseDateTime(row.startedAt) ?? /* @__PURE__ */ new Date(),
3079
3086
  completedAt: parseDateTime(row.completedAt) ?? /* @__PURE__ */ new Date(),
3080
3087
  retryCount: row.retryCount,
3088
+ attempt: row.attempt != null ? Number(row.attempt) : 0,
3081
3089
  traceId: row.traceId ?? null,
3082
3090
  status: row.status ?? null,
3083
3091
  tags: row.tags ? parseJSON$2(row.tags) ?? null : null,
@@ -3098,8 +3106,9 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3098
3106
  agentVersion: input.agentVersion ?? null,
3099
3107
  organizationId: input.organizationId ?? null,
3100
3108
  projectId: input.projectId ?? null,
3101
- targetType: input.targetType,
3102
- targetId: input.targetId,
3109
+ targetType: input.targetType ?? null,
3110
+ targetId: input.targetId ?? null,
3111
+ scorerIds: input.scorerIds ? JSON.stringify(input.scorerIds) : null,
3103
3112
  name: input.name ?? null,
3104
3113
  description: input.description ?? null,
3105
3114
  metadata: input.metadata ? JSON.stringify(input.metadata) : null,
@@ -3127,8 +3136,9 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3127
3136
  agentVersion: input.agentVersion ?? null,
3128
3137
  organizationId: input.organizationId ?? null,
3129
3138
  projectId: input.projectId ?? null,
3130
- targetType: input.targetType,
3131
- targetId: input.targetId,
3139
+ targetType: input.targetType ?? null,
3140
+ targetId: input.targetId ?? null,
3141
+ scorerIds: input.scorerIds ?? null,
3132
3142
  name: input.name,
3133
3143
  description: input.description,
3134
3144
  metadata: input.metadata,
@@ -3372,12 +3382,13 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3372
3382
  organizationId: input.organizationId ?? null,
3373
3383
  projectId: input.projectId ?? null,
3374
3384
  input: JSON.stringify(input.input),
3375
- output: input.output ? JSON.stringify(input.output) : null,
3376
- groundTruth: input.groundTruth ? JSON.stringify(input.groundTruth) : null,
3385
+ output: input.output != null ? JSON.stringify(input.output) : null,
3386
+ groundTruth: input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
3377
3387
  error: input.error ? JSON.stringify(input.error) : null,
3378
3388
  startedAt: input.startedAt,
3379
3389
  completedAt: input.completedAt,
3380
3390
  retryCount: input.retryCount,
3391
+ attempt: input.attempt ?? 0,
3381
3392
  traceId: input.traceId ?? null,
3382
3393
  status: input.status ?? null,
3383
3394
  tags: input.tags ? JSON.stringify(input.tags) : null,
@@ -3398,6 +3409,7 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3398
3409
  startedAt: input.startedAt,
3399
3410
  completedAt: input.completedAt,
3400
3411
  retryCount: input.retryCount,
3412
+ attempt: input.attempt ?? 0,
3401
3413
  traceId: input.traceId ?? null,
3402
3414
  status: input.status ?? null,
3403
3415
  tags: input.tags ?? null,
@@ -3430,6 +3442,66 @@ var ExperimentsMySQL = class ExperimentsMySQL extends _mastra_core_storage.Exper
3430
3442
  }, error);
3431
3443
  }
3432
3444
  }
3445
+ async upsertExperimentResult(input) {
3446
+ if (input.toolMockReport) throw new _mastra_core_error.MastraError({
3447
+ id: "MYSQL_EXPERIMENT_TOOL_MOCK_REPORT_UNSUPPORTED",
3448
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
3449
+ category: _mastra_core_error.ErrorCategory.USER,
3450
+ text: "Tool mock reports are not supported on the MySQL storage adapter. Use a supported adapter (LibSQL, PostgreSQL, MongoDB, or Spanner) to persist experiment tool mock reports."
3451
+ });
3452
+ try {
3453
+ const attempt = input.attempt ?? 0;
3454
+ const existingId = (await this.operations.query(`SELECT ${quoteIdentifier("id", "column name")} FROM ${formatTableName(_mastra_core_storage.TABLE_EXPERIMENT_RESULTS)} WHERE ${quoteIdentifier("experimentId", "column name")} = ? AND ${quoteIdentifier("itemId", "column name")} = ? AND COALESCE(${quoteIdentifier("attempt", "column name")}, 0) = ?`, [
3455
+ input.experimentId,
3456
+ input.itemId,
3457
+ attempt
3458
+ ]))[0]?.id;
3459
+ if (!existingId) return await this.addExperimentResult({
3460
+ ...input,
3461
+ attempt
3462
+ });
3463
+ await this.operations.update({
3464
+ tableName: _mastra_core_storage.TABLE_EXPERIMENT_RESULTS,
3465
+ keys: { id: existingId },
3466
+ data: {
3467
+ itemDatasetVersion: input.itemDatasetVersion ?? null,
3468
+ organizationId: input.organizationId ?? null,
3469
+ projectId: input.projectId ?? null,
3470
+ input: JSON.stringify(input.input),
3471
+ output: input.output != null ? JSON.stringify(input.output) : null,
3472
+ groundTruth: input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
3473
+ error: input.error ? JSON.stringify(input.error) : null,
3474
+ startedAt: input.startedAt,
3475
+ completedAt: input.completedAt,
3476
+ retryCount: input.retryCount,
3477
+ attempt,
3478
+ traceId: input.traceId ?? null,
3479
+ status: input.status ?? null,
3480
+ tags: input.tags ? JSON.stringify(input.tags) : null
3481
+ }
3482
+ });
3483
+ const updated = await this.getExperimentResultById({ id: existingId });
3484
+ if (!updated) throw new _mastra_core_error.MastraError({
3485
+ id: "MYSQL_UPSERT_EXPERIMENT_RESULT_NOT_FOUND",
3486
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
3487
+ category: _mastra_core_error.ErrorCategory.USER,
3488
+ text: `Experiment result ${existingId} not found after upsert`,
3489
+ details: {
3490
+ experimentId: input.experimentId,
3491
+ itemId: input.itemId,
3492
+ attempt
3493
+ }
3494
+ });
3495
+ return updated;
3496
+ } catch (error) {
3497
+ if (error instanceof _mastra_core_error.MastraError) throw error;
3498
+ throw new _mastra_core_error.MastraError({
3499
+ id: "MYSQL_UPSERT_EXPERIMENT_RESULT_FAILED",
3500
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
3501
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY
3502
+ }, error);
3503
+ }
3504
+ }
3433
3505
  async updateExperimentResult(input) {
3434
3506
  try {
3435
3507
  const existing = await this.operations.load({
@@ -9315,7 +9387,7 @@ var ScoresMySQL = class ScoresMySQL extends _mastra_core_storage.ScoresStorage {
9315
9387
  }
9316
9388
  async saveScore(score) {
9317
9389
  try {
9318
- const id = crypto.randomUUID();
9390
+ const id = score.id ?? crypto.randomUUID();
9319
9391
  const now = /* @__PURE__ */ new Date();
9320
9392
  await this.operations.insert({
9321
9393
  tableName: _mastra_core_storage.TABLE_SCORERS,
@@ -10529,9 +10601,15 @@ var WorkflowsMySQL = class WorkflowsMySQL extends _mastra_core_storage.Workflows
10529
10601
  await connection.rollback();
10530
10602
  return;
10531
10603
  }
10604
+ const existing = parseSnapshot(rows[0].snapshot);
10605
+ const { expectedStatus, ...state } = opts;
10606
+ if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(existing.status, expectedStatus)) {
10607
+ await connection.rollback();
10608
+ return;
10609
+ }
10532
10610
  const updatedSnapshot = {
10533
- ...parseSnapshot(rows[0].snapshot),
10534
- ...opts
10611
+ ...existing,
10612
+ ...state
10535
10613
  };
10536
10614
  await connection.execute(`UPDATE ${formatTableName(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)} SET ${quoteIdentifier("snapshot", "column name")} = ?, ${quoteIdentifier("updatedAt", "column name")} = ? WHERE ${quoteIdentifier("workflow_name", "column name")} = ? AND ${quoteIdentifier("run_id", "column name")} = ?`, [
10537
10615
  JSON.stringify(updatedSnapshot),