@mastra/spanner 1.6.4 → 1.6.5-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/dist/index.js CHANGED
@@ -2785,6 +2785,7 @@ function rowToItem(row) {
2785
2785
  tableName: TABLE_DATASET_ITEMS,
2786
2786
  row
2787
2787
  });
2788
+ const emptyValue = t.metadata?.__purged === true ? null : void 0;
2788
2789
  return {
2789
2790
  id: String(t.id),
2790
2791
  datasetId: String(t.datasetId),
@@ -2793,14 +2794,14 @@ function rowToItem(row) {
2793
2794
  organizationId: t.organizationId ?? null,
2794
2795
  projectId: t.projectId ?? null,
2795
2796
  input: t.input,
2796
- groundTruth: t.groundTruth ?? void 0,
2797
- expectedTrajectory: t.expectedTrajectory ?? void 0,
2798
- toolMocks: t.toolMocks ?? void 0,
2799
- unmockedToolPolicy: t.unmockedToolPolicy ?? void 0,
2800
- scorerIds: t.scorerIds ?? void 0,
2801
- requestContext: t.requestContext ?? void 0,
2797
+ groundTruth: t.groundTruth ?? emptyValue,
2798
+ expectedTrajectory: t.expectedTrajectory ?? emptyValue,
2799
+ toolMocks: t.toolMocks ?? emptyValue,
2800
+ unmockedToolPolicy: t.unmockedToolPolicy ?? emptyValue,
2801
+ scorerIds: t.scorerIds ?? emptyValue,
2802
+ requestContext: t.requestContext ?? emptyValue,
2802
2803
  metadata: t.metadata ?? void 0,
2803
- source: t.source ?? void 0,
2804
+ source: t.source ?? emptyValue,
2804
2805
  createdAt: toDate$1(t.createdAt),
2805
2806
  updatedAt: toDate$1(t.updatedAt)
2806
2807
  };
@@ -2946,21 +2947,17 @@ var DatasetsSpanner = class DatasetsSpanner extends DatasetsStorage {
2946
2947
  await this.db.clearTable({ tableName: TABLE_DATASETS });
2947
2948
  }
2948
2949
  async experimentTablesExist() {
2949
- try {
2950
- const [rows] = await this.database.run({
2951
- sql: `SELECT COUNT(*) AS c FROM INFORMATION_SCHEMA.TABLES
2952
- WHERE TABLE_SCHEMA = "" AND TABLE_NAME IN (@a, @b)`,
2953
- params: {
2954
- a: TABLE_EXPERIMENTS,
2955
- b: TABLE_EXPERIMENT_RESULTS
2956
- },
2957
- json: true
2958
- });
2959
- const row = rows?.[0];
2960
- return Number(row?.c ?? 0) === 2;
2961
- } catch {
2962
- return false;
2963
- }
2950
+ const [rows] = await this.database.run({
2951
+ sql: `SELECT COUNT(*) AS c FROM INFORMATION_SCHEMA.TABLES
2952
+ WHERE TABLE_SCHEMA = "" AND TABLE_NAME IN (@a, @b)`,
2953
+ params: {
2954
+ a: TABLE_EXPERIMENTS,
2955
+ b: TABLE_EXPERIMENT_RESULTS
2956
+ },
2957
+ json: true
2958
+ });
2959
+ const row = rows?.[0];
2960
+ return Number(row?.c ?? 0) === 2;
2964
2961
  }
2965
2962
  async createDataset(input) {
2966
2963
  try {
@@ -3574,6 +3571,91 @@ var DatasetsSpanner = class DatasetsSpanner extends DatasetsStorage {
3574
3571
  }, error);
3575
3572
  }
3576
3573
  }
3574
+ async _doPurgeItem({ id, datasetId }) {
3575
+ try {
3576
+ const experimentTablesExist = await this.experimentTablesExist();
3577
+ const purgedAt = (/* @__PURE__ */ new Date()).toISOString();
3578
+ await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
3579
+ try {
3580
+ const [datasetRowCount] = await tx.runUpdate({
3581
+ sql: `UPDATE ${quoteIdent(TABLE_DATASETS, "table name")}
3582
+ SET ${quoteIdent("version", "column name")} = ${quoteIdent("version", "column name")}
3583
+ WHERE ${quoteIdent("id", "column name")} = @datasetId`,
3584
+ params: { datasetId }
3585
+ });
3586
+ if (Number(datasetRowCount) === 0) {
3587
+ await tx.commit();
3588
+ return;
3589
+ }
3590
+ const [rows] = await tx.run({
3591
+ sql: `SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(TABLE_DATASET_ITEMS, "table name")} WHERE ${quoteIdent("id", "column name")} = @id AND ${quoteIdent("datasetId", "column name")} = @datasetId LIMIT 1`,
3592
+ params: {
3593
+ id,
3594
+ datasetId
3595
+ },
3596
+ json: true
3597
+ });
3598
+ if (!rows?.[0]) {
3599
+ await tx.commit();
3600
+ return;
3601
+ }
3602
+ await tx.runUpdate({
3603
+ sql: `UPDATE ${quoteIdent(TABLE_DATASET_ITEMS, "table name")} SET
3604
+ ${quoteIdent("input", "column name")} = JSON 'null',
3605
+ ${quoteIdent("groundTruth", "column name")} = NULL,
3606
+ ${quoteIdent("expectedTrajectory", "column name")} = NULL,
3607
+ ${quoteIdent("toolMocks", "column name")} = NULL,
3608
+ ${quoteIdent("unmockedToolPolicy", "column name")} = NULL,
3609
+ ${quoteIdent("scorerIds", "column name")} = NULL,
3610
+ ${quoteIdent("requestContext", "column name")} = NULL,
3611
+ ${quoteIdent("metadata", "column name")} = JSON_OBJECT('__purged', TRUE, 'purgedAt', @purgedAt),
3612
+ ${quoteIdent("source", "column name")} = NULL
3613
+ WHERE ${quoteIdent("id", "column name")} = @id AND ${quoteIdent("datasetId", "column name")} = @datasetId`,
3614
+ params: {
3615
+ id,
3616
+ datasetId,
3617
+ purgedAt
3618
+ }
3619
+ });
3620
+ if (experimentTablesExist) await tx.runUpdate({
3621
+ sql: `UPDATE ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")} SET
3622
+ ${quoteIdent("input", "column name")} = JSON 'null',
3623
+ ${quoteIdent("output", "column name")} = NULL,
3624
+ ${quoteIdent("groundTruth", "column name")} = NULL,
3625
+ ${quoteIdent("error", "column name")} = NULL,
3626
+ ${quoteIdent("toolMockReport", "column name")} = NULL,
3627
+ ${quoteIdent("tags", "column name")} = NULL,
3628
+ ${quoteIdent("comment", "column name")} = NULL,
3629
+ ${quoteIdent("metadata", "column name")} = JSON_OBJECT('__purged', TRUE, 'purgedAt', @purgedAt)
3630
+ WHERE ${quoteIdent("itemId", "column name")} = @id
3631
+ AND ${quoteIdent("experimentId", "column name")} IN (
3632
+ SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(TABLE_EXPERIMENTS, "table name")}
3633
+ WHERE ${quoteIdent("datasetId", "column name")} = @datasetId
3634
+ )`,
3635
+ params: {
3636
+ id,
3637
+ datasetId,
3638
+ purgedAt
3639
+ }
3640
+ });
3641
+ await tx.commit();
3642
+ } catch (err) {
3643
+ await tx.rollback().catch((rollbackErr) => {
3644
+ throw new AggregateError([err, rollbackErr], "Transaction and rollback both failed");
3645
+ });
3646
+ throw err;
3647
+ }
3648
+ }));
3649
+ } catch (error) {
3650
+ if (error instanceof MastraError) throw error;
3651
+ throw new MastraError({
3652
+ id: createStorageErrorId("SPANNER", "PURGE_ITEM", "FAILED"),
3653
+ domain: ErrorDomain.STORAGE,
3654
+ category: ErrorCategory.THIRD_PARTY,
3655
+ details: { id }
3656
+ }, error);
3657
+ }
3658
+ }
3577
3659
  async listItems(args) {
3578
3660
  const { page = 0, perPage: perPageInput } = args.pagination;
3579
3661
  const perPage = normalizePerPage(perPageInput, 100);
@@ -4059,6 +4141,48 @@ var ExperimentsSpanner = class ExperimentsSpanner extends ExperimentsStorage {
4059
4141
  this.skipDefaultIndexes = skipDefaultIndexes;
4060
4142
  this.indexes = indexes?.filter((idx) => ExperimentsSpanner.MANAGED_TABLES.includes(idx.table));
4061
4143
  }
4144
+ async #withPurgeBarrier(experimentId, itemId, fn) {
4145
+ return this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
4146
+ try {
4147
+ const [experimentRows] = await tx.run({
4148
+ sql: `SELECT ${quoteIdent("datasetId", "column name")} FROM ${quoteIdent(TABLE_EXPERIMENTS, "table name")}
4149
+ WHERE ${quoteIdent("id", "column name")} = @experimentId`,
4150
+ params: { experimentId },
4151
+ json: true
4152
+ });
4153
+ const datasetId = (experimentRows[0]?.toJSON?.() ?? experimentRows[0])?.datasetId;
4154
+ let purgeMetadata = null;
4155
+ if (datasetId) {
4156
+ await tx.runUpdate({
4157
+ sql: `UPDATE ${quoteIdent(TABLE_DATASETS, "table name")}
4158
+ SET ${quoteIdent("version", "column name")} = ${quoteIdent("version", "column name")}
4159
+ WHERE ${quoteIdent("id", "column name")} = @datasetId`,
4160
+ params: { datasetId }
4161
+ });
4162
+ const [itemRows] = await tx.run({
4163
+ sql: `SELECT ${quoteIdent("metadata", "column name")} FROM ${quoteIdent(TABLE_DATASET_ITEMS, "table name")}
4164
+ WHERE ${quoteIdent("id", "column name")} = @itemId AND ${quoteIdent("datasetId", "column name")} = @datasetId
4165
+ AND JSON_VALUE(${quoteIdent("metadata", "column name")}, '$.__purged') = 'true'
4166
+ LIMIT 1`,
4167
+ params: {
4168
+ itemId,
4169
+ datasetId
4170
+ },
4171
+ json: true
4172
+ });
4173
+ purgeMetadata = (itemRows[0]?.toJSON?.() ?? itemRows[0])?.metadata ?? null;
4174
+ }
4175
+ const result = await fn(tx, purgeMetadata);
4176
+ await tx.commit();
4177
+ return result;
4178
+ } catch (error) {
4179
+ await tx.rollback().catch((rollbackError) => {
4180
+ throw new AggregateError([error, rollbackError], "Transaction and rollback both failed");
4181
+ });
4182
+ throw error;
4183
+ }
4184
+ }));
4185
+ }
4062
4186
  async init() {
4063
4187
  await this.db.createTable({
4064
4188
  tableName: TABLE_EXPERIMENTS,
@@ -4473,54 +4597,83 @@ var ExperimentsSpanner = class ExperimentsSpanner extends ExperimentsStorage {
4473
4597
  try {
4474
4598
  const now = /* @__PURE__ */ new Date();
4475
4599
  const id = input.id ?? randomUUID();
4476
- const result = {
4477
- id,
4478
- experimentId: input.experimentId,
4479
- itemId: input.itemId,
4480
- itemDatasetVersion: input.itemDatasetVersion ?? null,
4481
- organizationId: input.organizationId ?? null,
4482
- projectId: input.projectId ?? null,
4483
- input: input.input ?? null,
4484
- output: input.output ?? null,
4485
- groundTruth: input.groundTruth ?? null,
4486
- metadata: input.metadata ?? null,
4487
- error: input.error ?? null,
4488
- startedAt: input.startedAt,
4489
- completedAt: input.completedAt,
4490
- retryCount: input.retryCount,
4491
- attempt: input.attempt ?? 0,
4492
- traceId: input.traceId ?? null,
4493
- status: input.status ?? null,
4494
- tags: input.tags ?? null,
4495
- toolMockReport: input.toolMockReport ?? null,
4496
- createdAt: now
4497
- };
4498
- await this.db.insert({
4499
- tableName: TABLE_EXPERIMENT_RESULTS,
4500
- record: {
4600
+ return await this.#withPurgeBarrier(input.experimentId, input.itemId, async (tx, marker) => {
4601
+ const result = {
4501
4602
  id,
4502
- experimentId: result.experimentId,
4503
- itemId: result.itemId,
4504
- itemDatasetVersion: result.itemDatasetVersion,
4505
- organizationId: result.organizationId,
4506
- projectId: result.projectId,
4507
- input: result.input,
4508
- output: result.output,
4509
- groundTruth: result.groundTruth,
4510
- metadata: result.metadata,
4511
- error: result.error,
4512
- startedAt: result.startedAt,
4513
- completedAt: result.completedAt,
4514
- retryCount: result.retryCount,
4515
- attempt: result.attempt,
4516
- traceId: result.traceId,
4517
- status: result.status,
4518
- tags: result.tags,
4519
- toolMockReport: result.toolMockReport,
4603
+ experimentId: input.experimentId,
4604
+ itemId: input.itemId,
4605
+ itemDatasetVersion: input.itemDatasetVersion ?? null,
4606
+ organizationId: input.organizationId ?? null,
4607
+ projectId: input.projectId ?? null,
4608
+ input: marker ? null : input.input ?? null,
4609
+ output: marker ? null : input.output ?? null,
4610
+ groundTruth: marker ? null : input.groundTruth ?? null,
4611
+ metadata: marker ?? input.metadata ?? null,
4612
+ error: marker ? null : input.error ?? null,
4613
+ startedAt: input.startedAt,
4614
+ completedAt: input.completedAt,
4615
+ retryCount: input.retryCount,
4616
+ attempt: input.attempt ?? 0,
4617
+ traceId: input.traceId ?? null,
4618
+ status: input.status ?? null,
4619
+ tags: marker ? null : input.tags ?? null,
4620
+ toolMockReport: marker ? null : input.toolMockReport ?? null,
4520
4621
  createdAt: now
4521
- }
4622
+ };
4623
+ await tx.runUpdate({
4624
+ sql: `INSERT INTO ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")} (
4625
+ ${[
4626
+ "id",
4627
+ "experimentId",
4628
+ "itemId",
4629
+ "itemDatasetVersion",
4630
+ "organizationId",
4631
+ "projectId",
4632
+ "input",
4633
+ "output",
4634
+ "groundTruth",
4635
+ "metadata",
4636
+ "error",
4637
+ "startedAt",
4638
+ "completedAt",
4639
+ "retryCount",
4640
+ "attempt",
4641
+ "traceId",
4642
+ "status",
4643
+ "tags",
4644
+ "toolMockReport",
4645
+ "createdAt"
4646
+ ].map((column) => quoteIdent(column, "column name")).join(", ")}
4647
+ ) VALUES (@id, @experimentId, @itemId, @itemDatasetVersion, @organizationId, @projectId,
4648
+ @input, @output, @groundTruth, @metadata, @error, @startedAt, @completedAt, @retryCount,
4649
+ @attempt, @traceId, @status, @tags, @toolMockReport, @createdAt)`,
4650
+ params: {
4651
+ ...result,
4652
+ input: JSON.stringify(result.input),
4653
+ output: JSON.stringify(result.output),
4654
+ groundTruth: JSON.stringify(result.groundTruth),
4655
+ metadata: JSON.stringify(result.metadata),
4656
+ error: JSON.stringify(result.error),
4657
+ tags: JSON.stringify(result.tags),
4658
+ toolMockReport: JSON.stringify(result.toolMockReport)
4659
+ },
4660
+ types: {
4661
+ itemDatasetVersion: "int64",
4662
+ organizationId: "string",
4663
+ projectId: "string",
4664
+ input: "json",
4665
+ output: "json",
4666
+ groundTruth: "json",
4667
+ metadata: "json",
4668
+ error: "json",
4669
+ traceId: "string",
4670
+ status: "string",
4671
+ tags: "json",
4672
+ toolMockReport: "json"
4673
+ }
4674
+ });
4675
+ return result;
4522
4676
  });
4523
- return result;
4524
4677
  } catch (error) {
4525
4678
  throw new MastraError({
4526
4679
  id: createStorageErrorId("SPANNER", "ADD_EXPERIMENT_RESULT", "FAILED"),
@@ -4536,31 +4689,12 @@ var ExperimentsSpanner = class ExperimentsSpanner extends ExperimentsStorage {
4536
4689
  async upsertExperimentResult(input) {
4537
4690
  try {
4538
4691
  const attempt = input.attempt ?? 0;
4539
- const [rows] = await this.database.run({
4540
- sql: `SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")}
4541
- WHERE ${quoteIdent("experimentId", "column name")} = @experimentId
4542
- AND ${quoteIdent("itemId", "column name")} = @itemId
4543
- AND COALESCE(${quoteIdent("attempt", "column name")}, 0) = @attempt`,
4544
- params: {
4545
- experimentId: input.experimentId,
4546
- itemId: input.itemId,
4547
- attempt
4548
- },
4549
- json: true
4550
- });
4551
- let existingId = rows[0]?.id;
4552
- if (!existingId) try {
4553
- return await this.addExperimentResult({
4554
- ...input,
4555
- attempt
4556
- });
4557
- } catch (insertError) {
4558
- if (!hasErrorCode(insertError, /* @__PURE__ */ new Set([6]))) throw insertError;
4559
- const [winnerRows] = await this.database.run({
4560
- sql: `SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")}
4561
- WHERE ${quoteIdent("experimentId", "column name")} = @experimentId
4562
- AND ${quoteIdent("itemId", "column name")} = @itemId
4563
- AND COALESCE(${quoteIdent("attempt", "column name")}, 0) = @attempt`,
4692
+ return await this.#withPurgeBarrier(input.experimentId, input.itemId, async (tx, marker) => {
4693
+ const [rows] = await tx.run({
4694
+ sql: `SELECT * FROM ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")}
4695
+ WHERE ${quoteIdent("experimentId", "column name")} = @experimentId
4696
+ AND ${quoteIdent("itemId", "column name")} = @itemId
4697
+ AND COALESCE(${quoteIdent("attempt", "column name")}, 0) = @attempt`,
4564
4698
  params: {
4565
4699
  experimentId: input.experimentId,
4566
4700
  itemId: input.itemId,
@@ -4568,71 +4702,97 @@ var ExperimentsSpanner = class ExperimentsSpanner extends ExperimentsStorage {
4568
4702
  },
4569
4703
  json: true
4570
4704
  });
4571
- existingId = winnerRows[0]?.id;
4572
- if (!existingId) throw insertError;
4573
- }
4574
- await this.db.runDml({
4575
- sql: `UPDATE ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")} SET
4576
- ${quoteIdent("itemDatasetVersion", "column name")} = @itemDatasetVersion,
4577
- ${quoteIdent("organizationId", "column name")} = @organizationId,
4578
- ${quoteIdent("projectId", "column name")} = @projectId,
4579
- ${quoteIdent("input", "column name")} = @input,
4580
- ${quoteIdent("output", "column name")} = @output,
4581
- ${quoteIdent("groundTruth", "column name")} = @groundTruth,
4582
- ${quoteIdent("metadata", "column name")} = @metadata,
4583
- ${quoteIdent("error", "column name")} = @error,
4584
- ${quoteIdent("startedAt", "column name")} = @startedAt,
4585
- ${quoteIdent("completedAt", "column name")} = @completedAt,
4586
- ${quoteIdent("retryCount", "column name")} = @retryCount,
4587
- ${quoteIdent("attempt", "column name")} = @attempt,
4588
- ${quoteIdent("traceId", "column name")} = @traceId,
4589
- ${quoteIdent("status", "column name")} = @status,
4590
- ${quoteIdent("tags", "column name")} = @tags,
4591
- ${quoteIdent("toolMockReport", "column name")} = @toolMockReport
4592
- WHERE ${quoteIdent("id", "column name")} = @id`,
4593
- params: {
4594
- id: existingId,
4705
+ const existingRow = rows[0]?.toJSON?.() ?? rows[0];
4706
+ const values = {
4595
4707
  itemDatasetVersion: input.itemDatasetVersion ?? null,
4596
4708
  organizationId: input.organizationId ?? null,
4597
4709
  projectId: input.projectId ?? null,
4598
- input: JSON.stringify(input.input),
4599
- output: input.output == null ? null : JSON.stringify(input.output),
4600
- groundTruth: input.groundTruth == null ? null : JSON.stringify(input.groundTruth),
4601
- metadata: input.metadata == null ? null : JSON.stringify(input.metadata),
4602
- error: input.error == null ? null : JSON.stringify(input.error),
4710
+ input: marker ? null : input.input,
4711
+ output: marker ? null : input.output ?? null,
4712
+ groundTruth: marker ? null : input.groundTruth ?? null,
4713
+ metadata: marker ?? input.metadata ?? null,
4714
+ error: marker ? null : input.error ?? null,
4603
4715
  startedAt: input.startedAt,
4604
4716
  completedAt: input.completedAt,
4605
4717
  retryCount: input.retryCount,
4606
4718
  attempt,
4607
4719
  traceId: input.traceId ?? null,
4608
4720
  status: input.status ?? null,
4609
- tags: input.tags == null ? null : JSON.stringify(input.tags),
4610
- toolMockReport: input.toolMockReport == null ? null : JSON.stringify(input.toolMockReport)
4611
- },
4612
- types: {
4613
- itemDatasetVersion: "int64",
4614
- organizationId: "string",
4615
- projectId: "string",
4616
- input: "json",
4617
- output: "json",
4618
- groundTruth: "json",
4619
- metadata: "json",
4620
- error: "json",
4621
- traceId: "string",
4622
- status: "string",
4623
- tags: "json",
4624
- toolMockReport: "json"
4625
- }
4626
- });
4627
- const updated = await this.getExperimentResultById({ id: existingId });
4628
- if (!updated) throw new MastraError({
4629
- id: createStorageErrorId("SPANNER", "UPSERT_EXPERIMENT_RESULT", "NOT_FOUND"),
4630
- domain: ErrorDomain.STORAGE,
4631
- category: ErrorCategory.USER,
4632
- text: `Experiment result ${existingId} not found after upsert`,
4633
- details: { id: existingId }
4721
+ tags: marker ? null : input.tags ?? null,
4722
+ toolMockReport: marker ? null : input.toolMockReport ?? null
4723
+ };
4724
+ if (!existingRow) {
4725
+ const result = {
4726
+ id: randomUUID(),
4727
+ experimentId: input.experimentId,
4728
+ itemId: input.itemId,
4729
+ ...values,
4730
+ createdAt: /* @__PURE__ */ new Date()
4731
+ };
4732
+ await this.db.insert({
4733
+ tableName: TABLE_EXPERIMENT_RESULTS,
4734
+ record: result,
4735
+ transaction: tx
4736
+ });
4737
+ return result;
4738
+ }
4739
+ const existing = rowToExperimentResult(existingRow);
4740
+ await this.db.runDml({
4741
+ sql: `UPDATE ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")} SET
4742
+ ${quoteIdent("itemDatasetVersion", "column name")} = @itemDatasetVersion,
4743
+ ${quoteIdent("organizationId", "column name")} = @organizationId,
4744
+ ${quoteIdent("projectId", "column name")} = @projectId,
4745
+ ${quoteIdent("input", "column name")} = @input,
4746
+ ${quoteIdent("output", "column name")} = @output,
4747
+ ${quoteIdent("groundTruth", "column name")} = @groundTruth,
4748
+ ${quoteIdent("metadata", "column name")} = @metadata,
4749
+ ${quoteIdent("error", "column name")} = @error,
4750
+ ${quoteIdent("startedAt", "column name")} = @startedAt,
4751
+ ${quoteIdent("completedAt", "column name")} = @completedAt,
4752
+ ${quoteIdent("retryCount", "column name")} = @retryCount,
4753
+ ${quoteIdent("attempt", "column name")} = @attempt,
4754
+ ${quoteIdent("traceId", "column name")} = @traceId,
4755
+ ${quoteIdent("status", "column name")} = @status,
4756
+ ${quoteIdent("tags", "column name")} = @tags,
4757
+ ${quoteIdent("toolMockReport", "column name")} = @toolMockReport,
4758
+ ${quoteIdent("comment", "column name")} = @comment
4759
+ WHERE ${quoteIdent("id", "column name")} = @id`,
4760
+ params: {
4761
+ id: existing.id,
4762
+ ...values,
4763
+ input: JSON.stringify(values.input),
4764
+ output: JSON.stringify(values.output),
4765
+ groundTruth: JSON.stringify(values.groundTruth),
4766
+ metadata: JSON.stringify(values.metadata),
4767
+ error: JSON.stringify(values.error),
4768
+ tags: JSON.stringify(values.tags),
4769
+ toolMockReport: JSON.stringify(values.toolMockReport),
4770
+ comment: marker ? null : existing.comment
4771
+ },
4772
+ types: {
4773
+ itemDatasetVersion: "int64",
4774
+ retryCount: "int64",
4775
+ attempt: "int64",
4776
+ organizationId: "string",
4777
+ projectId: "string",
4778
+ input: "json",
4779
+ output: "json",
4780
+ groundTruth: "json",
4781
+ metadata: "json",
4782
+ error: "json",
4783
+ traceId: "string",
4784
+ status: "string",
4785
+ tags: "json",
4786
+ toolMockReport: "json",
4787
+ comment: "string"
4788
+ }
4789
+ }, tx);
4790
+ return {
4791
+ ...existing,
4792
+ ...values,
4793
+ comment: marker ? null : existing.comment
4794
+ };
4634
4795
  });
4635
- return updated;
4636
4796
  } catch (error) {
4637
4797
  if (error instanceof MastraError) throw error;
4638
4798
  throw new MastraError({
@@ -4648,61 +4808,47 @@ var ExperimentsSpanner = class ExperimentsSpanner extends ExperimentsStorage {
4648
4808
  }
4649
4809
  async updateExperimentResult(input) {
4650
4810
  try {
4651
- if (input.status === void 0 && input.tags === void 0 && input.comment === void 0) {
4652
- const existing = await this.getExperimentResultById({ id: input.id });
4653
- if (!existing || input.experimentId !== void 0 && existing.experimentId !== input.experimentId) throw new MastraError({
4654
- id: createStorageErrorId("SPANNER", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"),
4655
- domain: ErrorDomain.STORAGE,
4656
- category: ErrorCategory.USER,
4657
- text: `Experiment result ${input.id} not found`,
4658
- details: { id: input.id }
4659
- });
4660
- return existing;
4661
- }
4662
- const setClauses = [];
4663
- const params = { id: input.id };
4664
- const types = {};
4665
- if (input.status !== void 0) {
4666
- setClauses.push(`${quoteIdent("status", "column name")} = @status`);
4667
- params.status = input.status;
4668
- if (input.status === null) types.status = "string";
4669
- }
4670
- if (input.tags !== void 0) {
4671
- setClauses.push(`${quoteIdent("tags", "column name")} = @tags`);
4672
- params.tags = input.tags === null ? null : JSON.stringify(input.tags);
4673
- types.tags = "json";
4674
- }
4675
- if (input.comment !== void 0) {
4676
- setClauses.push(`${quoteIdent("comment", "column name")} = @comment`);
4677
- params.comment = input.comment;
4678
- if (input.comment === null) types.comment = "string";
4679
- }
4680
- const whereClauses = [`${quoteIdent("id", "column name")} = @id`];
4681
- if (input.experimentId !== void 0) {
4682
- whereClauses.push(`${quoteIdent("experimentId", "column name")} = @experimentId`);
4683
- params.experimentId = input.experimentId;
4684
- }
4685
- if (await this.db.runDml({
4686
- sql: `UPDATE ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")}
4687
- SET ${setClauses.join(", ")} WHERE ${whereClauses.join(" AND ")}`,
4688
- params,
4689
- types
4690
- }) === 0) throw new MastraError({
4811
+ const existing = await this.getExperimentResultById({ id: input.id });
4812
+ if (!existing || input.experimentId !== void 0 && existing.experimentId !== input.experimentId) throw new MastraError({
4691
4813
  id: createStorageErrorId("SPANNER", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"),
4692
4814
  domain: ErrorDomain.STORAGE,
4693
4815
  category: ErrorCategory.USER,
4694
4816
  text: `Experiment result ${input.id} not found`,
4695
4817
  details: { id: input.id }
4696
4818
  });
4697
- const updated = await this.getExperimentResultById({ id: input.id });
4698
- if (!updated) throw new MastraError({
4699
- id: createStorageErrorId("SPANNER", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"),
4700
- domain: ErrorDomain.STORAGE,
4701
- category: ErrorCategory.USER,
4702
- text: `Experiment result ${input.id} not found`,
4703
- details: { id: input.id }
4819
+ return await this.#withPurgeBarrier(existing.experimentId, existing.itemId, async (tx, marker) => {
4820
+ const updated = {
4821
+ ...existing,
4822
+ status: input.status !== void 0 ? input.status : existing.status,
4823
+ tags: marker ? null : input.tags !== void 0 ? input.tags : existing.tags,
4824
+ comment: marker ? null : input.comment !== void 0 ? input.comment : existing.comment
4825
+ };
4826
+ if (await this.db.runDml({
4827
+ sql: `UPDATE ${quoteIdent(TABLE_EXPERIMENT_RESULTS, "table name")}
4828
+ SET ${quoteIdent("status", "column name")} = @status,
4829
+ ${quoteIdent("tags", "column name")} = @tags,
4830
+ ${quoteIdent("comment", "column name")} = @comment
4831
+ WHERE ${quoteIdent("id", "column name")} = @id${input.experimentId ? ` AND ${quoteIdent("experimentId", "column name")} = @experimentId` : ""}`,
4832
+ params: {
4833
+ id: input.id,
4834
+ ...input.experimentId ? { experimentId: input.experimentId } : {},
4835
+ status: updated.status,
4836
+ tags: updated.tags === null ? null : JSON.stringify(updated.tags),
4837
+ comment: updated.comment
4838
+ },
4839
+ types: {
4840
+ status: "string",
4841
+ tags: "json",
4842
+ comment: "string"
4843
+ }
4844
+ }, tx) === 0) throw new MastraError({
4845
+ id: createStorageErrorId("SPANNER", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"),
4846
+ domain: ErrorDomain.STORAGE,
4847
+ category: ErrorCategory.USER,
4848
+ details: { id: input.id }
4849
+ });
4850
+ return updated;
4704
4851
  });
4705
- return updated;
4706
4852
  } catch (error) {
4707
4853
  if (error instanceof MastraError) throw error;
4708
4854
  throw new MastraError({
@@ -9078,19 +9224,34 @@ var ObservabilitySpanner = class ObservabilitySpanner extends ObservabilityStora
9078
9224
  }
9079
9225
  }
9080
9226
  async batchDeleteTraces(args) {
9227
+ this.assertUnscopedBatchDeleteTraces(args);
9081
9228
  try {
9082
9229
  if (args.traceIds.length === 0) return;
9083
- const tableName = quoteIdent(TABLE_SPANS, "table name");
9084
9230
  const params = {};
9085
9231
  const placeholders = args.traceIds.map((id, i) => {
9086
9232
  const name = `t${i}`;
9087
9233
  params[name] = id;
9088
9234
  return `@${name}`;
9089
9235
  });
9090
- await this.db.runDml({
9091
- sql: `DELETE FROM ${tableName} WHERE ${quoteIdent("traceId", "column name")} IN (${placeholders.join(", ")})`,
9092
- params
9093
- });
9236
+ const traceIdFilter = `${quoteIdent("traceId", "column name")} IN (${placeholders.join(", ")})`;
9237
+ await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
9238
+ try {
9239
+ await tx.runUpdate({
9240
+ sql: `DELETE FROM ${quoteIdent(TABLE_SPANS, "table name")} WHERE ${traceIdFilter}`,
9241
+ params
9242
+ });
9243
+ if (!this.disableMetrics) await tx.runUpdate({
9244
+ sql: `DELETE FROM ${quoteIdent(TABLE_AI_METRICS, "table name")} WHERE ${traceIdFilter}`,
9245
+ params
9246
+ });
9247
+ await tx.commit();
9248
+ } catch (error) {
9249
+ await tx.rollback().catch((rollbackError) => {
9250
+ throw new AggregateError([error, rollbackError], "Transaction and rollback both failed");
9251
+ });
9252
+ throw error;
9253
+ }
9254
+ }));
9094
9255
  } catch (error) {
9095
9256
  throw new MastraError({
9096
9257
  id: createStorageErrorId("SPANNER", "BATCH_DELETE_TRACES", "FAILED"),