@mastra/libsql 1.22.5 → 1.23.0-alpha.2

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
@@ -1849,6 +1849,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${TABLE_SPANS}\n================
1849
1849
  this.logger.debug(`LibSQLDB: Created unique index on (spanId, traceId) for ${TABLE_SPANS}`);
1850
1850
  }
1851
1851
  }
1852
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "mastra_ai_spans_roots_started_at_idx" ON "${TABLE_SPANS}" ("startedAt" DESC) WHERE "parentSpanId" IS NULL`);
1853
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "mastra_ai_spans_trace_started_at_idx" ON "${TABLE_SPANS}" ("traceId", "startedAt")`);
1854
+ this.logger.debug(`LibSQLDB: Ensured trace read indexes for ${TABLE_SPANS}`);
1852
1855
  this.logger.info(`LibSQLDB: Migration completed for ${TABLE_SPANS}`);
1853
1856
  } catch (error) {
1854
1857
  if (error instanceof MastraError) throw error;
@@ -3020,8 +3023,15 @@ function resolveTargets({ policies, descriptor, order }) {
3020
3023
  //#endregion
3021
3024
  //#region src/storage/domains/background-tasks/index.ts
3022
3025
  function serializeJson(v) {
3023
- if (typeof v === "object" && v != null) return JSON.stringify(v);
3024
- return v ?? null;
3026
+ if (v === void 0) return null;
3027
+ let serialized;
3028
+ try {
3029
+ serialized = JSON.stringify(v);
3030
+ } catch (error) {
3031
+ throw new Error("Failed to serialize background task value as JSON", { cause: error });
3032
+ }
3033
+ if (serialized === void 0) throw new Error("Failed to serialize background task value as JSON");
3034
+ return serialized;
3025
3035
  }
3026
3036
  function parseJson$4(val) {
3027
3037
  if (val == null) return void 0;
@@ -4140,94 +4150,122 @@ var DatasetsLibSQL = class extends DatasetsStorage {
4140
4150
  }
4141
4151
  async _doUpdateItem(args) {
4142
4152
  try {
4143
- const existing = await this.getItemById({ id: args.id });
4144
- if (!existing) throw new MastraError({
4145
- id: createStorageErrorId("LIBSQL", "UPDATE_ITEM", "NOT_FOUND"),
4146
- domain: ErrorDomain.STORAGE,
4147
- category: ErrorCategory.USER,
4148
- details: { itemId: args.id }
4149
- });
4150
- if (existing.datasetId !== args.datasetId) throw new MastraError({
4151
- id: createStorageErrorId("LIBSQL", "UPDATE_ITEM", "DATASET_MISMATCH"),
4152
- domain: ErrorDomain.STORAGE,
4153
- category: ErrorCategory.USER,
4154
- details: {
4155
- itemId: args.id,
4156
- expectedDatasetId: args.datasetId,
4157
- actualDatasetId: existing.datasetId
4153
+ return await withClientWriteLock(this.#client, async () => {
4154
+ const tx = await this.#client.transaction("write");
4155
+ try {
4156
+ const itemResult = await tx.execute({
4157
+ sql: `SELECT ${buildSelectColumns(TABLE_DATASET_ITEMS)} FROM ${TABLE_DATASET_ITEMS} WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4158
+ args: [args.id]
4159
+ });
4160
+ const existing = itemResult.rows[0] ? this.transformItemRow(itemResult.rows[0]) : null;
4161
+ if (!existing) throw new MastraError({
4162
+ id: createStorageErrorId("LIBSQL", "UPDATE_ITEM", "NOT_FOUND"),
4163
+ domain: ErrorDomain.STORAGE,
4164
+ category: ErrorCategory.USER,
4165
+ details: { itemId: args.id }
4166
+ });
4167
+ if (existing.datasetId !== args.datasetId) throw new MastraError({
4168
+ id: createStorageErrorId("LIBSQL", "UPDATE_ITEM", "DATASET_MISMATCH"),
4169
+ domain: ErrorDomain.STORAGE,
4170
+ category: ErrorCategory.USER,
4171
+ details: {
4172
+ itemId: args.id,
4173
+ expectedDatasetId: args.datasetId,
4174
+ actualDatasetId: existing.datasetId
4175
+ }
4176
+ });
4177
+ if (existing.metadata?.__purged === true) throw new MastraError({
4178
+ id: "DATASET_ITEM_PURGED",
4179
+ domain: ErrorDomain.STORAGE,
4180
+ category: ErrorCategory.USER,
4181
+ details: {
4182
+ datasetId: args.datasetId,
4183
+ itemId: args.id
4184
+ },
4185
+ text: `Purged dataset item cannot be updated: ${args.id}`
4186
+ });
4187
+ const dataset = (await tx.execute({
4188
+ sql: `SELECT organizationId, projectId FROM ${TABLE_DATASETS} WHERE id = ?`,
4189
+ args: [args.datasetId]
4190
+ })).rows[0];
4191
+ const organizationId = typeof dataset?.organizationId === "string" ? dataset.organizationId : null;
4192
+ const projectId = typeof dataset?.projectId === "string" ? dataset.projectId : null;
4193
+ const versionId = crypto.randomUUID();
4194
+ const now = /* @__PURE__ */ new Date();
4195
+ const nowIso = now.toISOString();
4196
+ const mergedInput = args.input !== void 0 ? args.input : existing.input;
4197
+ const mergedGroundTruth = args.groundTruth !== void 0 ? args.groundTruth : existing.groundTruth;
4198
+ const mergedExpectedTrajectory = args.expectedTrajectory !== void 0 ? args.expectedTrajectory : existing.expectedTrajectory;
4199
+ const mergedToolMocks = args.toolMocks !== void 0 ? args.toolMocks : existing.toolMocks;
4200
+ const mergedUnmockedToolPolicy = args.unmockedToolPolicy !== void 0 ? args.unmockedToolPolicy : existing.unmockedToolPolicy;
4201
+ const mergedScorerIds = args.scorerIds !== void 0 ? args.scorerIds ?? void 0 : existing.scorerIds;
4202
+ const mergedRequestContext = args.requestContext !== void 0 ? args.requestContext : existing.requestContext;
4203
+ const mergedMetadata = args.metadata !== void 0 ? args.metadata : existing.metadata;
4204
+ const mergedSource = args.source !== void 0 ? args.source : existing.source;
4205
+ const versionResult = await tx.execute({
4206
+ sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`,
4207
+ args: [args.datasetId]
4208
+ });
4209
+ const newVersion = Number(versionResult.rows[0].version);
4210
+ await tx.execute({
4211
+ sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = ? WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4212
+ args: [newVersion, args.id]
4213
+ });
4214
+ await tx.execute({
4215
+ sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,?,?,?,?,NULL,0,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4216
+ args: [
4217
+ args.id,
4218
+ args.datasetId,
4219
+ newVersion,
4220
+ existing.externalId ?? null,
4221
+ organizationId,
4222
+ projectId,
4223
+ jsonbArg(mergedInput),
4224
+ jsonbArg(mergedGroundTruth),
4225
+ jsonbArg(mergedExpectedTrajectory),
4226
+ jsonbArg(mergedToolMocks),
4227
+ mergedUnmockedToolPolicy ?? null,
4228
+ jsonbArg(mergedScorerIds),
4229
+ jsonbArg(mergedRequestContext),
4230
+ jsonbArg(mergedMetadata),
4231
+ jsonbArg(mergedSource),
4232
+ existing.createdAt.toISOString(),
4233
+ nowIso
4234
+ ]
4235
+ });
4236
+ await tx.execute({
4237
+ sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, ?, ?)`,
4238
+ args: [
4239
+ versionId,
4240
+ args.datasetId,
4241
+ newVersion,
4242
+ nowIso
4243
+ ]
4244
+ });
4245
+ await tx.commit();
4246
+ return {
4247
+ ...existing,
4248
+ datasetVersion: newVersion,
4249
+ organizationId,
4250
+ projectId,
4251
+ input: mergedInput,
4252
+ groundTruth: mergedGroundTruth,
4253
+ expectedTrajectory: mergedExpectedTrajectory,
4254
+ toolMocks: mergedToolMocks,
4255
+ unmockedToolPolicy: mergedUnmockedToolPolicy,
4256
+ scorerIds: mergedScorerIds,
4257
+ requestContext: mergedRequestContext,
4258
+ metadata: mergedMetadata,
4259
+ source: mergedSource,
4260
+ updatedAt: now
4261
+ };
4262
+ } catch (error) {
4263
+ if (!tx.closed) await tx.rollback().catch((rollbackError) => {
4264
+ throw new AggregateError([error, rollbackError], "Transaction and rollback both failed");
4265
+ });
4266
+ throw error;
4158
4267
  }
4159
4268
  });
4160
- const dataset = await this.getDatasetById({ id: args.datasetId });
4161
- const versionId = crypto.randomUUID();
4162
- const now = /* @__PURE__ */ new Date();
4163
- const nowIso = now.toISOString();
4164
- const mergedInput = args.input !== void 0 ? args.input : existing.input;
4165
- const mergedGroundTruth = args.groundTruth !== void 0 ? args.groundTruth : existing.groundTruth;
4166
- const mergedExpectedTrajectory = args.expectedTrajectory !== void 0 ? args.expectedTrajectory : existing.expectedTrajectory;
4167
- const mergedToolMocks = args.toolMocks !== void 0 ? args.toolMocks : existing.toolMocks;
4168
- const mergedUnmockedToolPolicy = args.unmockedToolPolicy !== void 0 ? args.unmockedToolPolicy : existing.unmockedToolPolicy;
4169
- const mergedScorerIds = args.scorerIds !== void 0 ? args.scorerIds ?? void 0 : existing.scorerIds;
4170
- const mergedRequestContext = args.requestContext !== void 0 ? args.requestContext : existing.requestContext;
4171
- const mergedMetadata = args.metadata !== void 0 ? args.metadata : existing.metadata;
4172
- const mergedSource = args.source !== void 0 ? args.source : existing.source;
4173
- const results = await this.#client.batch([
4174
- {
4175
- sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`,
4176
- args: [args.datasetId]
4177
- },
4178
- {
4179
- sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4180
- args: [args.datasetId, args.id]
4181
- },
4182
- {
4183
- sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,(SELECT version FROM ${TABLE_DATASETS} WHERE id = ?),?,(SELECT organizationId FROM ${TABLE_DATASETS} WHERE id = ?),(SELECT projectId FROM ${TABLE_DATASETS} WHERE id = ?),NULL,0,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4184
- args: [
4185
- args.id,
4186
- args.datasetId,
4187
- args.datasetId,
4188
- existing.externalId ?? null,
4189
- args.datasetId,
4190
- args.datasetId,
4191
- jsonbArg(mergedInput),
4192
- jsonbArg(mergedGroundTruth),
4193
- jsonbArg(mergedExpectedTrajectory),
4194
- jsonbArg(mergedToolMocks),
4195
- mergedUnmockedToolPolicy ?? null,
4196
- jsonbArg(mergedScorerIds),
4197
- jsonbArg(mergedRequestContext),
4198
- jsonbArg(mergedMetadata),
4199
- jsonbArg(mergedSource),
4200
- existing.createdAt.toISOString(),
4201
- nowIso
4202
- ]
4203
- },
4204
- {
4205
- sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?), ?)`,
4206
- args: [
4207
- versionId,
4208
- args.datasetId,
4209
- args.datasetId,
4210
- nowIso
4211
- ]
4212
- }
4213
- ], "write");
4214
- const newVersion = Number(results[0].rows[0].version);
4215
- return {
4216
- ...existing,
4217
- datasetVersion: newVersion,
4218
- organizationId: dataset?.organizationId ?? null,
4219
- projectId: dataset?.projectId ?? null,
4220
- input: mergedInput,
4221
- groundTruth: mergedGroundTruth,
4222
- expectedTrajectory: mergedExpectedTrajectory,
4223
- toolMocks: mergedToolMocks,
4224
- unmockedToolPolicy: mergedUnmockedToolPolicy,
4225
- scorerIds: mergedScorerIds,
4226
- requestContext: mergedRequestContext,
4227
- metadata: mergedMetadata,
4228
- source: mergedSource,
4229
- updatedAt: now
4230
- };
4231
4269
  } catch (error) {
4232
4270
  if (error instanceof MastraError) throw error;
4233
4271
  throw new MastraError({
@@ -4239,61 +4277,78 @@ var DatasetsLibSQL = class extends DatasetsStorage {
4239
4277
  }
4240
4278
  async _doDeleteItem({ id, datasetId }) {
4241
4279
  try {
4242
- const existing = await this.getItemById({ id });
4243
- if (!existing) return;
4244
- if (existing.datasetId !== datasetId) throw new MastraError({
4245
- id: createStorageErrorId("LIBSQL", "DELETE_ITEM", "DATASET_MISMATCH"),
4246
- domain: ErrorDomain.STORAGE,
4247
- category: ErrorCategory.USER,
4248
- details: {
4249
- itemId: id,
4250
- expectedDatasetId: datasetId,
4251
- actualDatasetId: existing.datasetId
4280
+ await withClientWriteLock(this.#client, async () => {
4281
+ const tx = await this.#client.transaction("write");
4282
+ try {
4283
+ const itemResult = await tx.execute({
4284
+ sql: `SELECT ${buildSelectColumns(TABLE_DATASET_ITEMS)} FROM ${TABLE_DATASET_ITEMS} WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4285
+ args: [id]
4286
+ });
4287
+ const existing = itemResult.rows[0] ? this.transformItemRow(itemResult.rows[0]) : null;
4288
+ if (!existing) {
4289
+ await tx.commit();
4290
+ return;
4291
+ }
4292
+ if (existing.datasetId !== datasetId) throw new MastraError({
4293
+ id: createStorageErrorId("LIBSQL", "DELETE_ITEM", "DATASET_MISMATCH"),
4294
+ domain: ErrorDomain.STORAGE,
4295
+ category: ErrorCategory.USER,
4296
+ details: {
4297
+ itemId: id,
4298
+ expectedDatasetId: datasetId,
4299
+ actualDatasetId: existing.datasetId
4300
+ }
4301
+ });
4302
+ const versionId = crypto.randomUUID();
4303
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4304
+ const dataset = (await tx.execute({
4305
+ sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version, organizationId, projectId`,
4306
+ args: [datasetId]
4307
+ })).rows[0];
4308
+ const newVersion = Number(dataset.version);
4309
+ await tx.execute({
4310
+ sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = ? WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4311
+ args: [newVersion, id]
4312
+ });
4313
+ await tx.execute({
4314
+ sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,?,?,?,?,NULL,1,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4315
+ args: [
4316
+ id,
4317
+ datasetId,
4318
+ newVersion,
4319
+ existing.externalId ?? null,
4320
+ dataset.organizationId ?? null,
4321
+ dataset.projectId ?? null,
4322
+ jsonbArg(existing.input),
4323
+ jsonbArg(existing.groundTruth),
4324
+ jsonbArg(existing.expectedTrajectory),
4325
+ jsonbArg(existing.toolMocks),
4326
+ existing.unmockedToolPolicy ?? null,
4327
+ jsonbArg(existing.scorerIds),
4328
+ jsonbArg(existing.requestContext),
4329
+ jsonbArg(existing.metadata),
4330
+ jsonbArg(existing.source),
4331
+ existing.createdAt.toISOString(),
4332
+ nowIso
4333
+ ]
4334
+ });
4335
+ await tx.execute({
4336
+ sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, ?, ?)`,
4337
+ args: [
4338
+ versionId,
4339
+ datasetId,
4340
+ newVersion,
4341
+ nowIso
4342
+ ]
4343
+ });
4344
+ await tx.commit();
4345
+ } catch (error) {
4346
+ if (!tx.closed) await tx.rollback().catch((rollbackError) => {
4347
+ throw new AggregateError([error, rollbackError], "Transaction and rollback both failed");
4348
+ });
4349
+ throw error;
4252
4350
  }
4253
4351
  });
4254
- const versionId = crypto.randomUUID();
4255
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4256
- await this.#client.batch([
4257
- {
4258
- sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`,
4259
- args: [datasetId]
4260
- },
4261
- {
4262
- sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4263
- args: [datasetId, id]
4264
- },
4265
- {
4266
- sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,(SELECT version FROM ${TABLE_DATASETS} WHERE id = ?),?,(SELECT organizationId FROM ${TABLE_DATASETS} WHERE id = ?),(SELECT projectId FROM ${TABLE_DATASETS} WHERE id = ?),NULL,1,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4267
- args: [
4268
- id,
4269
- datasetId,
4270
- datasetId,
4271
- existing.externalId ?? null,
4272
- datasetId,
4273
- datasetId,
4274
- jsonbArg(existing.input),
4275
- jsonbArg(existing.groundTruth),
4276
- jsonbArg(existing.expectedTrajectory),
4277
- jsonbArg(existing.toolMocks),
4278
- existing.unmockedToolPolicy ?? null,
4279
- jsonbArg(existing.scorerIds),
4280
- jsonbArg(existing.requestContext),
4281
- jsonbArg(existing.metadata),
4282
- jsonbArg(existing.source),
4283
- existing.createdAt.toISOString(),
4284
- nowIso
4285
- ]
4286
- },
4287
- {
4288
- sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?), ?)`,
4289
- args: [
4290
- versionId,
4291
- datasetId,
4292
- datasetId,
4293
- nowIso
4294
- ]
4295
- }
4296
- ], "write");
4297
4352
  } catch (error) {
4298
4353
  if (error instanceof MastraError) throw error;
4299
4354
  throw new MastraError({
@@ -4711,63 +4766,84 @@ var DatasetsLibSQL = class extends DatasetsStorage {
4711
4766
  }
4712
4767
  async _doBatchDeleteItems(input) {
4713
4768
  try {
4714
- const dataset = await this.getDatasetById({ id: input.datasetId });
4715
- if (!dataset) throw new MastraError({
4716
- id: createStorageErrorId("LIBSQL", "BULK_DELETE_ITEMS", "DATASET_NOT_FOUND"),
4717
- domain: ErrorDomain.STORAGE,
4718
- category: ErrorCategory.USER,
4719
- details: { datasetId: input.datasetId }
4720
- });
4721
- const currentItems = [];
4722
- for (const itemId of input.itemIds) {
4723
- const item = await this.getItemById({ id: itemId });
4724
- if (item && item.datasetId === input.datasetId) currentItems.push(item);
4725
- }
4726
- if (currentItems.length === 0) return;
4727
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4728
- const versionId = crypto.randomUUID();
4729
- const statements = [{
4730
- sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`,
4731
- args: [input.datasetId]
4732
- }];
4733
- for (const item of currentItems) {
4734
- statements.push({
4735
- sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4736
- args: [input.datasetId, item.id]
4737
- });
4738
- statements.push({
4739
- sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,(SELECT version FROM ${TABLE_DATASETS} WHERE id = ?),?,?,?,NULL,1,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4740
- args: [
4741
- item.id,
4742
- input.datasetId,
4743
- input.datasetId,
4744
- item.externalId ?? null,
4745
- dataset.organizationId ?? null,
4746
- dataset.projectId ?? null,
4747
- jsonbArg(item.input),
4748
- jsonbArg(item.groundTruth),
4749
- jsonbArg(item.expectedTrajectory),
4750
- jsonbArg(item.toolMocks),
4751
- item.unmockedToolPolicy ?? null,
4752
- jsonbArg(item.scorerIds),
4753
- jsonbArg(item.requestContext),
4754
- jsonbArg(item.metadata),
4755
- jsonbArg(item.source),
4756
- item.createdAt.toISOString(),
4757
- nowIso
4758
- ]
4759
- });
4760
- }
4761
- statements.push({
4762
- sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?), ?)`,
4763
- args: [
4764
- versionId,
4765
- input.datasetId,
4766
- input.datasetId,
4767
- nowIso
4768
- ]
4769
+ await withClientWriteLock(this.#client, async () => {
4770
+ const tx = await this.#client.transaction("write");
4771
+ try {
4772
+ const dataset = (await tx.execute({
4773
+ sql: `SELECT organizationId, projectId FROM ${TABLE_DATASETS} WHERE id = ?`,
4774
+ args: [input.datasetId]
4775
+ })).rows[0];
4776
+ if (!dataset) throw new MastraError({
4777
+ id: createStorageErrorId("LIBSQL", "BULK_DELETE_ITEMS", "DATASET_NOT_FOUND"),
4778
+ domain: ErrorDomain.STORAGE,
4779
+ category: ErrorCategory.USER,
4780
+ details: { datasetId: input.datasetId }
4781
+ });
4782
+ const currentItems = [];
4783
+ for (const itemId of input.itemIds) {
4784
+ const itemResult = await tx.execute({
4785
+ sql: `SELECT ${buildSelectColumns(TABLE_DATASET_ITEMS)} FROM ${TABLE_DATASET_ITEMS} WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4786
+ args: [itemId]
4787
+ });
4788
+ const item = itemResult.rows[0] ? this.transformItemRow(itemResult.rows[0]) : null;
4789
+ if (item && item.datasetId === input.datasetId) currentItems.push(item);
4790
+ }
4791
+ if (currentItems.length === 0) {
4792
+ await tx.commit();
4793
+ return;
4794
+ }
4795
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4796
+ const versionId = crypto.randomUUID();
4797
+ const statements = [{
4798
+ sql: `UPDATE ${TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`,
4799
+ args: [input.datasetId]
4800
+ }];
4801
+ for (const item of currentItems) {
4802
+ statements.push({
4803
+ sql: `UPDATE ${TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
4804
+ args: [input.datasetId, item.id]
4805
+ });
4806
+ statements.push({
4807
+ sql: `INSERT INTO ${TABLE_DATASET_ITEMS} (id,datasetId,datasetVersion,externalId,organizationId,projectId,validTo,isDeleted,input,groundTruth,expectedTrajectory,toolMocks,unmockedToolPolicy,scorerIds,requestContext,metadata,source,createdAt,updatedAt) VALUES (?,?,(SELECT version FROM ${TABLE_DATASETS} WHERE id = ?),?,?,?,NULL,1,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,jsonb(?),jsonb(?),jsonb(?),jsonb(?),?,?)`,
4808
+ args: [
4809
+ item.id,
4810
+ input.datasetId,
4811
+ input.datasetId,
4812
+ item.externalId ?? null,
4813
+ dataset.organizationId ?? null,
4814
+ dataset.projectId ?? null,
4815
+ jsonbArg(item.input),
4816
+ jsonbArg(item.groundTruth),
4817
+ jsonbArg(item.expectedTrajectory),
4818
+ jsonbArg(item.toolMocks),
4819
+ item.unmockedToolPolicy ?? null,
4820
+ jsonbArg(item.scorerIds),
4821
+ jsonbArg(item.requestContext),
4822
+ jsonbArg(item.metadata),
4823
+ jsonbArg(item.source),
4824
+ item.createdAt.toISOString(),
4825
+ nowIso
4826
+ ]
4827
+ });
4828
+ }
4829
+ statements.push({
4830
+ sql: `INSERT INTO ${TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${TABLE_DATASETS} WHERE id = ?), ?)`,
4831
+ args: [
4832
+ versionId,
4833
+ input.datasetId,
4834
+ input.datasetId,
4835
+ nowIso
4836
+ ]
4837
+ });
4838
+ for (const statement of statements) await tx.execute(statement);
4839
+ await tx.commit();
4840
+ } catch (error) {
4841
+ if (!tx.closed) await tx.rollback().catch((rollbackError) => {
4842
+ throw new AggregateError([error, rollbackError], "Transaction and rollback both failed");
4843
+ });
4844
+ throw error;
4845
+ }
4769
4846
  });
4770
- await this.#client.batch(statements, "write");
4771
4847
  } catch (error) {
4772
4848
  if (error instanceof MastraError) throw error;
4773
4849
  throw new MastraError({
@@ -8540,6 +8616,71 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8540
8616
  }, error);
8541
8617
  }
8542
8618
  }
8619
+ /**
8620
+ * Atomically reassign a thread and all of its messages to a different resource.
8621
+ *
8622
+ * Runs inside a single write transaction. SQLite serializes write transactions, so overlapping
8623
+ * transfers of the same thread cannot interleave the thread update with the message update:
8624
+ * either both the thread and every message move to the new resource, or neither does. The
8625
+ * thread's `createdAt` is preserved. Callers are responsible for authorizing the reassignment.
8626
+ */
8627
+ async updateThreadResourceId({ threadId, resourceId }) {
8628
+ try {
8629
+ const tx = await this.#client.transaction("write");
8630
+ try {
8631
+ const row = (await tx.execute({
8632
+ sql: `SELECT * FROM "${TABLE_THREADS}" WHERE id = ?`,
8633
+ args: [threadId]
8634
+ })).rows?.[0];
8635
+ if (!row) throw new Error(`Thread "${threadId}" not found`);
8636
+ const currentResourceId = row.resourceId;
8637
+ const normalized = {
8638
+ id: row.id,
8639
+ resourceId: currentResourceId,
8640
+ title: row.title,
8641
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
8642
+ createdAt: new Date(row.createdAt),
8643
+ updatedAt: new Date(row.updatedAt)
8644
+ };
8645
+ if (currentResourceId === resourceId) {
8646
+ await tx.commit();
8647
+ return normalized;
8648
+ }
8649
+ const now = /* @__PURE__ */ new Date();
8650
+ await tx.execute({
8651
+ sql: `UPDATE "${TABLE_THREADS}" SET "resourceId" = ?, "updatedAt" = ? WHERE id = ?`,
8652
+ args: [
8653
+ resourceId,
8654
+ now.toISOString(),
8655
+ threadId
8656
+ ]
8657
+ });
8658
+ await tx.execute({
8659
+ sql: `UPDATE "${TABLE_MESSAGES}" SET "resourceId" = ? WHERE thread_id = ?`,
8660
+ args: [resourceId, threadId]
8661
+ });
8662
+ await tx.commit();
8663
+ return {
8664
+ ...normalized,
8665
+ resourceId,
8666
+ updatedAt: now
8667
+ };
8668
+ } catch (error) {
8669
+ await tx.rollback();
8670
+ throw error;
8671
+ }
8672
+ } catch (error) {
8673
+ throw new MastraError({
8674
+ id: createStorageErrorId("LIBSQL", "UPDATE_THREAD_RESOURCE_ID", "FAILED"),
8675
+ domain: ErrorDomain.STORAGE,
8676
+ category: ErrorCategory.THIRD_PARTY,
8677
+ details: {
8678
+ threadId,
8679
+ resourceId
8680
+ }
8681
+ }, error);
8682
+ }
8683
+ }
8543
8684
  async listThreads(args) {
8544
8685
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
8545
8686
  try {
@@ -8728,7 +8869,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8728
8869
  }, error);
8729
8870
  }
8730
8871
  }
8731
- async cloneThread(args) {
8872
+ async copyThread(args) {
8732
8873
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
8733
8874
  const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
8734
8875
  if (!sourceThread) throw new MastraError({
@@ -8747,7 +8888,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8747
8888
  details: { newThreadId }
8748
8889
  });
8749
8890
  try {
8750
- let messageQuery = `SELECT id, content, role, type, "createdAt", thread_id, "resourceId"
8891
+ let messageQuery = `SELECT id, "createdAt"
8751
8892
  FROM "${TABLE_MESSAGES}" WHERE thread_id = ?`;
8752
8893
  const messageParams = [sourceThreadId];
8753
8894
  if (options?.messageFilter?.startDate) {
@@ -8805,52 +8946,28 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8805
8946
  nowStr
8806
8947
  ]
8807
8948
  });
8808
- const clonedMessages = [];
8809
8949
  const messageIdMap = {};
8810
8950
  const targetResourceId = resourceId || sourceThread.resourceId;
8811
8951
  for (const sourceMsg of sourceMessages) {
8812
8952
  const newMessageId = crypto.randomUUID();
8813
- messageIdMap[sourceMsg.id] = newMessageId;
8814
- const contentStr = sourceMsg.content;
8815
- let parsedContent;
8816
- try {
8817
- parsedContent = JSON.parse(contentStr);
8818
- } catch {
8819
- parsedContent = {
8820
- format: 2,
8821
- parts: [{
8822
- type: "text",
8823
- text: contentStr
8824
- }]
8825
- };
8826
- }
8827
- await tx.execute({
8953
+ const sourceMsgId = sourceMsg.id;
8954
+ messageIdMap[sourceMsgId] = newMessageId;
8955
+ const insertResult = await tx.execute({
8828
8956
  sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
8829
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
8957
+ SELECT ?, ?, content, role, type, "createdAt", ?
8958
+ FROM "${TABLE_MESSAGES}" WHERE id = ?`,
8830
8959
  args: [
8831
8960
  newMessageId,
8832
8961
  newThreadId,
8833
- contentStr,
8834
- sourceMsg.role,
8835
- sourceMsg.type || "v2",
8836
- sourceMsg.createdAt,
8837
- targetResourceId
8962
+ targetResourceId,
8963
+ sourceMsgId
8838
8964
  ]
8839
8965
  });
8840
- clonedMessages.push({
8841
- id: newMessageId,
8842
- threadId: newThreadId,
8843
- content: parsedContent,
8844
- role: sourceMsg.role,
8845
- type: sourceMsg.type || void 0,
8846
- createdAt: new Date(sourceMsg.createdAt),
8847
- resourceId: targetResourceId
8848
- });
8966
+ if (insertResult.rowsAffected !== 1) throw new Error(`Failed to copy message ${sourceMsgId}: expected 1 row copied but got ${insertResult.rowsAffected}`);
8849
8967
  }
8850
8968
  await tx.commit();
8851
8969
  return {
8852
8970
  thread: newThread,
8853
- clonedMessages,
8854
8971
  messageIdMap
8855
8972
  };
8856
8973
  } catch (error) {