@mastra/libsql 1.22.6-alpha.0 → 1.23.0-alpha.3

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;
@@ -8606,6 +8616,71 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8606
8616
  }, error);
8607
8617
  }
8608
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
+ }
8609
8684
  async listThreads(args) {
8610
8685
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
8611
8686
  try {
@@ -8794,7 +8869,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8794
8869
  }, error);
8795
8870
  }
8796
8871
  }
8797
- async cloneThread(args) {
8872
+ async copyThread(args) {
8798
8873
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
8799
8874
  const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
8800
8875
  if (!sourceThread) throw new MastraError({
@@ -8812,9 +8887,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8812
8887
  text: `Thread with id ${newThreadId} already exists`,
8813
8888
  details: { newThreadId }
8814
8889
  });
8815
- const hydrateMessages = options?.hydrateMessages ?? true;
8816
8890
  try {
8817
- let messageQuery = `SELECT ${hydrateMessages ? `id, content, role, type, "createdAt", thread_id, "resourceId"` : `id, "createdAt"`}
8891
+ let messageQuery = `SELECT id, "createdAt"
8818
8892
  FROM "${TABLE_MESSAGES}" WHERE thread_id = ?`;
8819
8893
  const messageParams = [sourceThreadId];
8820
8894
  if (options?.messageFilter?.startDate) {
@@ -8872,68 +8946,28 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8872
8946
  nowStr
8873
8947
  ]
8874
8948
  });
8875
- const clonedMessages = [];
8876
8949
  const messageIdMap = {};
8877
8950
  const targetResourceId = resourceId || sourceThread.resourceId;
8878
8951
  for (const sourceMsg of sourceMessages) {
8879
8952
  const newMessageId = crypto.randomUUID();
8880
8953
  const sourceMsgId = sourceMsg.id;
8881
8954
  messageIdMap[sourceMsgId] = newMessageId;
8882
- if (!hydrateMessages) {
8883
- const insertResult = await tx.execute({
8884
- sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
8885
- SELECT ?, ?, content, role, type, "createdAt", ?
8886
- FROM "${TABLE_MESSAGES}" WHERE id = ?`,
8887
- args: [
8888
- newMessageId,
8889
- newThreadId,
8890
- targetResourceId,
8891
- sourceMsgId
8892
- ]
8893
- });
8894
- if (insertResult.rowsAffected !== 1) throw new Error(`Failed to clone message ${sourceMsgId}: expected 1 row copied but got ${insertResult.rowsAffected}`);
8895
- continue;
8896
- }
8897
- const contentStr = sourceMsg.content;
8898
- let parsedContent;
8899
- try {
8900
- parsedContent = JSON.parse(contentStr);
8901
- } catch {
8902
- parsedContent = {
8903
- format: 2,
8904
- parts: [{
8905
- type: "text",
8906
- text: contentStr
8907
- }]
8908
- };
8909
- }
8910
- await tx.execute({
8955
+ const insertResult = await tx.execute({
8911
8956
  sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
8912
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
8957
+ SELECT ?, ?, content, role, type, "createdAt", ?
8958
+ FROM "${TABLE_MESSAGES}" WHERE id = ?`,
8913
8959
  args: [
8914
8960
  newMessageId,
8915
8961
  newThreadId,
8916
- contentStr,
8917
- sourceMsg.role,
8918
- sourceMsg.type || "v2",
8919
- sourceMsg.createdAt,
8920
- targetResourceId
8962
+ targetResourceId,
8963
+ sourceMsgId
8921
8964
  ]
8922
8965
  });
8923
- clonedMessages.push({
8924
- id: newMessageId,
8925
- threadId: newThreadId,
8926
- content: parsedContent,
8927
- role: sourceMsg.role,
8928
- type: sourceMsg.type || void 0,
8929
- createdAt: new Date(sourceMsg.createdAt),
8930
- resourceId: targetResourceId
8931
- });
8966
+ if (insertResult.rowsAffected !== 1) throw new Error(`Failed to copy message ${sourceMsgId}: expected 1 row copied but got ${insertResult.rowsAffected}`);
8932
8967
  }
8933
8968
  await tx.commit();
8934
8969
  return {
8935
8970
  thread: newThread,
8936
- clonedMessages,
8937
8971
  messageIdMap
8938
8972
  };
8939
8973
  } catch (error) {
@@ -9903,6 +9937,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9903
9937
  };
9904
9938
  //#endregion
9905
9939
  //#region src/storage/domains/notifications/index.ts
9940
+ /** Ids per UPDATE statement — keeps bind parameters well under SQLite's per-statement limit. */
9941
+ const BULK_ID_BATCH_SIZE = 500;
9906
9942
  const statusTimestamp = (status, now) => {
9907
9943
  if (status === "delivered") return { deliveredAt: now };
9908
9944
  if (status === "seen") return { seenAt: now };
@@ -10203,6 +10239,31 @@ var NotificationsLibSQL = class NotificationsLibSQL extends NotificationsStorage
10203
10239
  if (!updated) throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`);
10204
10240
  return updated;
10205
10241
  }
10242
+ async updateNotificationsStatus(input) {
10243
+ const ids = Array.from(new Set(input.ids));
10244
+ if (ids.length === 0) return [];
10245
+ const now = /* @__PURE__ */ new Date();
10246
+ const assignments = {
10247
+ status: input.status,
10248
+ updatedAt: now.toISOString()
10249
+ };
10250
+ for (const [column, value] of Object.entries(statusTimestamp(input.status, now))) assignments[column] = value.toISOString();
10251
+ const setClause = Object.keys(assignments).map((column) => `"${column}" = ?`).join(", ");
10252
+ const updated = [];
10253
+ for (let offset = 0; offset < ids.length; offset += BULK_ID_BATCH_SIZE) {
10254
+ const batch = ids.slice(offset, offset + BULK_ID_BATCH_SIZE);
10255
+ const result = await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
10256
+ sql: `UPDATE "${TABLE_NOTIFICATIONS}" SET ${setClause} WHERE "threadId" = ? AND "id" IN (${batch.map(() => "?").join(", ")}) RETURNING ${buildSelectColumns(TABLE_NOTIFICATIONS)}`,
10257
+ args: [
10258
+ ...Object.values(assignments),
10259
+ input.threadId,
10260
+ ...batch
10261
+ ]
10262
+ })), `bulk update notification status in table ${TABLE_NOTIFICATIONS}`);
10263
+ for (const row of result.rows ?? []) updated.push(rowToNotification(row));
10264
+ }
10265
+ return updated;
10266
+ }
10206
10267
  async findCoalescable(input) {
10207
10268
  if (!input.dedupeKey && !input.coalesceKey) return void 0;
10208
10269
  const agentId = input.agentId ?? null;