@mastra/pg 1.24.1-alpha.0 → 1.25.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
@@ -10668,6 +10668,57 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10668
10668
  }, error);
10669
10669
  }
10670
10670
  }
10671
+ /**
10672
+ * Atomically reassign a thread and all of its messages to a different resource.
10673
+ *
10674
+ * Runs inside a single transaction and takes a `SELECT ... FOR UPDATE` row lock on the
10675
+ * thread, so overlapping transfers of the same thread serialize and can never interleave
10676
+ * the thread update with the message update. Either both the thread and every message move
10677
+ * to the new resource, or neither does — there is no split-ownership window. The thread's
10678
+ * `createdAt` is preserved. Callers are responsible for authorizing the reassignment.
10679
+ */
10680
+ async updateThreadResourceId({ threadId, resourceId }) {
10681
+ const threadsTable = getTableName$3({
10682
+ indexName: TABLE_THREADS,
10683
+ schemaName: getSchemaName$3(this.#schema)
10684
+ });
10685
+ const messagesTable = getTableName$3({
10686
+ indexName: TABLE_MESSAGES,
10687
+ schemaName: getSchemaName$3(this.#schema)
10688
+ });
10689
+ try {
10690
+ return await this.#db.client.tx(async (t) => {
10691
+ const thread = await t.oneOrNone(`SELECT * FROM ${threadsTable} WHERE id = $1 FOR UPDATE`, [threadId]);
10692
+ if (!thread) throw new Error(`Thread "${threadId}" not found`);
10693
+ const normalized = {
10694
+ id: thread.id,
10695
+ resourceId: thread.resourceId,
10696
+ title: thread.title,
10697
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
10698
+ createdAt: thread.createdAtZ || thread.createdAt,
10699
+ updatedAt: thread.updatedAtZ || thread.updatedAt
10700
+ };
10701
+ if (thread.resourceId === resourceId) return normalized;
10702
+ await t.none(`UPDATE ${threadsTable} SET "resourceId" = $1, "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id = $2`, [resourceId, threadId]);
10703
+ await t.none(`UPDATE ${messagesTable} SET "resourceId" = $1 WHERE thread_id = $2`, [resourceId, threadId]);
10704
+ return {
10705
+ ...normalized,
10706
+ resourceId,
10707
+ updatedAt: /* @__PURE__ */ new Date()
10708
+ };
10709
+ });
10710
+ } catch (error) {
10711
+ throw new MastraError({
10712
+ id: createStorageErrorId("PG", "UPDATE_THREAD_RESOURCE_ID", "FAILED"),
10713
+ domain: ErrorDomain.STORAGE,
10714
+ category: ErrorCategory.THIRD_PARTY,
10715
+ details: {
10716
+ threadId,
10717
+ resourceId
10718
+ }
10719
+ }, error);
10720
+ }
10721
+ }
10671
10722
  async listThreads(args) {
10672
10723
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
10673
10724
  try {
@@ -11672,7 +11723,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11672
11723
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
11673
11724
  return updatedResource;
11674
11725
  }
11675
- async cloneThread(args) {
11726
+ async copyThread(args) {
11676
11727
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
11677
11728
  const sourceThread = await this.#getThreadById(this.#db.client, { threadId: sourceThreadId });
11678
11729
  if (!sourceThread) throw new MastraError({
@@ -11698,10 +11749,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11698
11749
  indexName: TABLE_MESSAGES,
11699
11750
  schemaName: getSchemaName$3(this.#schema)
11700
11751
  });
11701
- const hydrateMessages = options?.hydrateMessages ?? true;
11702
11752
  try {
11703
11753
  return await this.#db.client.tx(async (t) => {
11704
- let messageQuery = `SELECT ${hydrateMessages ? `id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"` : `id, "createdAt"`}
11754
+ let messageQuery = `SELECT id, "createdAt"
11705
11755
  FROM ${messageTableName} WHERE thread_id = $1`;
11706
11756
  const messageParams = [sourceThreadId];
11707
11757
  let paramIndex = 2;
@@ -11762,54 +11812,23 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11762
11812
  nowStr,
11763
11813
  nowStr
11764
11814
  ]);
11765
- const clonedMessages = [];
11766
11815
  const messageIdMap = {};
11767
11816
  const targetResourceId = resourceId || sourceThread.resourceId;
11768
11817
  for (const sourceMsg of sourceMessages) {
11769
11818
  const newMessageId = crypto.randomUUID();
11770
11819
  messageIdMap[sourceMsg.id] = newMessageId;
11771
- if (!hydrateMessages) {
11772
- const insertResult = await t.query(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11773
- SELECT $1, $2, content, "createdAt", "createdAtZ", role, type, $3
11774
- FROM ${messageTableName} WHERE id = $4`, [
11775
- newMessageId,
11776
- newThreadId,
11777
- targetResourceId,
11778
- sourceMsg.id
11779
- ]);
11780
- if (insertResult.rowCount !== 1) throw new Error(`Failed to clone message ${sourceMsg.id}: expected 1 row copied but got ${insertResult.rowCount}`);
11781
- continue;
11782
- }
11783
- const normalizedMsg = this.normalizeMessageRow(sourceMsg);
11784
- let parsedContent = normalizedMsg.content;
11785
- try {
11786
- parsedContent = JSON.parse(normalizedMsg.content);
11787
- } catch {}
11788
- const createdAt = toUtcISOString(new Date(normalizedMsg.createdAt));
11789
- await t.none(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11790
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
11820
+ const insertResult = await t.query(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11821
+ SELECT $1, $2, content, "createdAt", "createdAtZ", role, type, $3
11822
+ FROM ${messageTableName} WHERE id = $4`, [
11791
11823
  newMessageId,
11792
11824
  newThreadId,
11793
- typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
11794
- createdAt,
11795
- createdAt,
11796
- normalizedMsg.role,
11797
- normalizedMsg.type || "v2",
11798
- targetResourceId
11825
+ targetResourceId,
11826
+ sourceMsg.id
11799
11827
  ]);
11800
- clonedMessages.push({
11801
- id: newMessageId,
11802
- threadId: newThreadId,
11803
- content: parsedContent,
11804
- role: normalizedMsg.role,
11805
- type: normalizedMsg.type,
11806
- createdAt: new Date(normalizedMsg.createdAt),
11807
- resourceId: targetResourceId
11808
- });
11828
+ if (insertResult.rowCount !== 1) throw new Error(`Failed to copy message ${sourceMsg.id}: expected 1 row copied but got ${insertResult.rowCount}`);
11809
11829
  }
11810
11830
  return {
11811
11831
  thread: newThread,
11812
- clonedMessages,
11813
11832
  messageIdMap
11814
11833
  };
11815
11834
  });
@@ -13164,6 +13183,27 @@ var NotificationsPG = class NotificationsPG extends NotificationsStorage {
13164
13183
  if (!updated) throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`);
13165
13184
  return updated;
13166
13185
  }
13186
+ async updateNotificationsStatus(input) {
13187
+ const ids = Array.from(new Set(input.ids));
13188
+ if (ids.length === 0) return [];
13189
+ const now = /* @__PURE__ */ new Date();
13190
+ const assignments = {
13191
+ status: input.status,
13192
+ ...statusTimestamp(input.status, now),
13193
+ updatedAt: now
13194
+ };
13195
+ const columns = Object.keys(assignments);
13196
+ const setClause = columns.map((column, index) => `"${parseSqlIdentifier(column, "column name")}" = $${index + 1}`).join(", ");
13197
+ const tableName = getTableName$5({
13198
+ indexName: TABLE_NOTIFICATIONS,
13199
+ schemaName: getSchemaName$5(this.#schema)
13200
+ });
13201
+ return (await this.#db.client.manyOrNone(`UPDATE ${tableName} SET ${setClause} WHERE "threadId" = $${columns.length + 1} AND "id" = ANY($${columns.length + 2}::text[]) RETURNING *`, [
13202
+ ...Object.values(assignments),
13203
+ input.threadId,
13204
+ ids
13205
+ ])).map(rowToNotification);
13206
+ }
13167
13207
  async findCoalescable(input) {
13168
13208
  if (!input.dedupeKey && !input.coalesceKey) return void 0;
13169
13209
  const tableName = getTableName$5({
@@ -17318,6 +17358,13 @@ function collectRelationCollections(predicate, collections = /* @__PURE__ */ new
17318
17358
  else if (predicate.type === "not") collectRelationCollections(predicate.arg, collections);
17319
17359
  return collections;
17320
17360
  }
17361
+ function collectThreadRelationCollections(predicate, collections) {
17362
+ if (!predicate) return collections;
17363
+ if (predicate.type === "relation") collectRelationCollections(predicate.predicate, collections);
17364
+ else if (predicate.type === "boolean") for (const arg of predicate.args) collectThreadRelationCollections(arg, collections);
17365
+ else collectThreadRelationCollections(predicate.arg, collections);
17366
+ return collections;
17367
+ }
17321
17368
  function compilePredicate(predicate, parameterOffset) {
17322
17369
  if (predicate.type === "relation") {
17323
17370
  const compiled = predicate.collection === "feedback" ? compileFeedbackScalarPredicate(predicate.predicate, parameterOffset) : compileScalarPredicate(predicate.predicate, predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS, parameterOffset);
@@ -17352,12 +17399,41 @@ function compilePredicate(predicate, parameterOffset) {
17352
17399
  }
17353
17400
  return compileScalarPredicate(predicate, TRACE_FIELDS, parameterOffset, true);
17354
17401
  }
17355
- function compilePostgresTraceQuery(schema, plan) {
17402
+ function compileThreadPredicate(predicate, parameterOffset) {
17403
+ if (predicate.type === "relation") {
17404
+ const compiled = compilePredicate(predicate.predicate, parameterOffset);
17405
+ const existence = `EXISTS (
17406
+ SELECT 1 FROM eligible_roots r
17407
+ WHERE r."threadId" = t."threadId"
17408
+ AND (${compiled.sql})
17409
+ )`;
17410
+ return {
17411
+ sql: predicate.quantifier === "some" ? existence : `NOT ${existence}`,
17412
+ values: compiled.values
17413
+ };
17414
+ }
17415
+ if (predicate.type === "boolean") {
17416
+ const values = [];
17417
+ return {
17418
+ sql: predicate.args.map((arg) => {
17419
+ const compiled = compileThreadPredicate(arg, parameterOffset + values.length);
17420
+ values.push(...compiled.values);
17421
+ return `(${compiled.sql})`;
17422
+ }).join(predicate.operator === "and" ? " AND " : " OR "),
17423
+ values
17424
+ };
17425
+ }
17426
+ const compiled = compileThreadPredicate(predicate.arg, parameterOffset);
17427
+ return {
17428
+ sql: `NOT (${compiled.sql})`,
17429
+ values: compiled.values
17430
+ };
17431
+ }
17432
+ function compilePostgresTraceScope(schema, selection, relationCollections) {
17356
17433
  const spanTable = qualifiedTable(schema, TABLE_SPAN_EVENTS);
17357
17434
  const scoreTable = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17358
17435
  const feedbackTable = qualifiedTable(schema, TABLE_FEEDBACK_EVENTS);
17359
- const values = [plan.timeRange.from, plan.timeRange.to];
17360
- const relationCollections = collectRelationCollections(plan.where);
17436
+ const values = [selection.timeRange.from, selection.timeRange.to];
17361
17437
  const ctes = [`root_scope AS MATERIALIZED (
17362
17438
  SELECT *
17363
17439
  FROM ${spanTable} r
@@ -17432,6 +17508,13 @@ function compilePostgresTraceQuery(schema, plan) {
17432
17508
  AND s."traceId" IN (SELECT "traceId" FROM root_scope)
17433
17509
  AND ${latestFeedbackPredicate(feedbackTable)}
17434
17510
  )`);
17511
+ return {
17512
+ ctes,
17513
+ values
17514
+ };
17515
+ }
17516
+ function compilePostgresTraceQuery(schema, plan) {
17517
+ const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
17435
17518
  let predicateSql = "TRUE";
17436
17519
  if (plan.where) {
17437
17520
  const predicate = compilePredicate(plan.where, values.length + 1);
@@ -17479,6 +17562,51 @@ LIMIT $${values.length}`,
17479
17562
  values
17480
17563
  };
17481
17564
  }
17565
+ function compilePostgresThreadQuery(schema, plan) {
17566
+ const relationCollections = collectRelationCollections(plan.traces.where);
17567
+ collectThreadRelationCollections(plan.where, relationCollections);
17568
+ const { ctes, values } = compilePostgresTraceScope(schema, plan.traces, relationCollections);
17569
+ let eligibilitySql = "TRUE";
17570
+ if (plan.traces.where) {
17571
+ const eligibility = compilePredicate(plan.traces.where, values.length + 1);
17572
+ eligibilitySql = eligibility.sql;
17573
+ values.push(...eligibility.values);
17574
+ }
17575
+ ctes.push(`eligible_roots AS MATERIALIZED (
17576
+ SELECT *
17577
+ FROM root_scope r
17578
+ WHERE ${eligibilitySql}
17579
+ )`);
17580
+ ctes.push(`thread_ids AS (
17581
+ SELECT "threadId" COLLATE "C" AS "threadId"
17582
+ FROM eligible_roots
17583
+ WHERE "threadId" IS NOT NULL
17584
+ GROUP BY "threadId" COLLATE "C"
17585
+ )`);
17586
+ let threadPredicateSql = "TRUE";
17587
+ if (plan.where) {
17588
+ const predicate = compileThreadPredicate(plan.where, values.length + 1);
17589
+ threadPredicateSql = predicate.sql;
17590
+ values.push(...predicate.values);
17591
+ }
17592
+ ctes.push(`qualified_threads AS (
17593
+ SELECT t."threadId"
17594
+ FROM thread_ids t
17595
+ WHERE ${threadPredicateSql}
17596
+ )`);
17597
+ const pageCondition = plan.cursor ? `WHERE "threadId" > $${values.length + 1}` : "";
17598
+ if (plan.cursor) values.push(plan.cursor.threadId);
17599
+ values.push(plan.limit + 1);
17600
+ return {
17601
+ text: `WITH ${ctes.join(",\n")}
17602
+ SELECT "threadId"
17603
+ FROM qualified_threads
17604
+ ${pageCondition}
17605
+ ORDER BY "threadId" ASC
17606
+ LIMIT $${values.length}`,
17607
+ values
17608
+ };
17609
+ }
17482
17610
  function asIsoTimestamp$1(value) {
17483
17611
  if (value === null || value === void 0) throw new Error("Trace query returned a null timestamp");
17484
17612
  return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
@@ -17537,6 +17665,19 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17537
17665
  }) : null }
17538
17666
  });
17539
17667
  }
17668
+ async function queryThreads(client, schema, plan, timeoutMs) {
17669
+ const query = compilePostgresThreadQuery(schema, plan);
17670
+ const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17671
+ const threads = rows.slice(0, plan.limit).map((row) => ({ threadId: String(row.threadId) }));
17672
+ const last = threads.at(-1);
17673
+ return coreStorage.queryThreadsResultSchema.parse({
17674
+ threads,
17675
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
17676
+ result: "threads",
17677
+ threadId: last.threadId
17678
+ }) : null }
17679
+ });
17680
+ }
17540
17681
  //#endregion
17541
17682
  //#region src/storage/domains/observability/v-next/traces.ts
17542
17683
  /**
@@ -18432,13 +18573,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18432
18573
  if (!deltaPollingFeatureEnabled()) return [
18433
18574
  "metrics",
18434
18575
  "logs",
18435
- "trace-query"
18576
+ "trace-query",
18577
+ "thread-query"
18436
18578
  ];
18437
18579
  return [
18438
18580
  "metrics",
18439
18581
  "logs",
18440
18582
  "delta-polling",
18441
- "trace-query"
18583
+ "trace-query",
18584
+ "thread-query"
18442
18585
  ];
18443
18586
  }
18444
18587
  async #run(op, fn, details) {
@@ -18484,6 +18627,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18484
18627
  async queryTraces(plan) {
18485
18628
  return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18486
18629
  }
18630
+ async queryThreads(plan) {
18631
+ return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18632
+ }
18487
18633
  async listBranches(args) {
18488
18634
  return this.#run("LIST_BRANCHES", () => listBranches(this.#readClient, this.#schema, args));
18489
18635
  }