@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.
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.24.1-alpha.0"
6
+ version: "1.25.0-alpha.2"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.24.1-alpha.0",
2
+ "version": "1.25.0-alpha.2",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -39,7 +39,7 @@ export const agent = new Agent({
39
39
 
40
40
  **options** (`MemoryConfig`): Memory configuration options.
41
41
 
42
- **options.lastMessages** (`number | false`): Number of most recent messages to include in context. Set to false to disable the message history feature entirely (messages are not loaded into context or saved). Use Number.MAX\_SAFE\_INTEGER to retrieve all messages with no limit. To load messages without saving new ones, use the readOnly option.
42
+ **options.lastMessages** (`number | false`): Number of most recent messages to include in context. Set to false to disable the message history feature entirely (messages are not loaded into context or saved). Use Number.MAX\_SAFE\_INTEGER to retrieve all messages with no limit. To load messages without saving new ones, use the readOnly option. The window slides forward on every request, so once a thread exceeds the limit, each turn invalidates the provider prompt cache. For long-running conversations, use Observational Memory instead.
43
43
 
44
44
  **options.readOnly** (`boolean`): When true, prevents memory from saving new messages and provides working memory as read-only context (without the updateWorkingMemory tool). Useful for read-only operations like previews, internal routing agents, or sub agents that should reference but not modify memory.
45
45
 
@@ -147,5 +147,6 @@ export const agent = new Agent({
147
147
  - [listThreads](https://mastra.ai/reference/memory/listThreads)
148
148
  - [deleteMessages](https://mastra.ai/reference/memory/deleteMessages)
149
149
  - [cloneThread](https://mastra.ai/reference/memory/cloneThread)
150
+ - [copyThread](https://mastra.ai/reference/memory/copyThread)
150
151
  - [settled](https://mastra.ai/reference/memory/settled)
151
152
  - [Clone Utility Methods](https://mastra.ai/reference/memory/clone-utilities)
package/dist/index.cjs CHANGED
@@ -10692,6 +10692,57 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
10692
10692
  }, error);
10693
10693
  }
10694
10694
  }
10695
+ /**
10696
+ * Atomically reassign a thread and all of its messages to a different resource.
10697
+ *
10698
+ * Runs inside a single transaction and takes a `SELECT ... FOR UPDATE` row lock on the
10699
+ * thread, so overlapping transfers of the same thread serialize and can never interleave
10700
+ * the thread update with the message update. Either both the thread and every message move
10701
+ * to the new resource, or neither does — there is no split-ownership window. The thread's
10702
+ * `createdAt` is preserved. Callers are responsible for authorizing the reassignment.
10703
+ */
10704
+ async updateThreadResourceId({ threadId, resourceId }) {
10705
+ const threadsTable = getTableName$3({
10706
+ indexName: _mastra_core_storage.TABLE_THREADS,
10707
+ schemaName: getSchemaName$3(this.#schema)
10708
+ });
10709
+ const messagesTable = getTableName$3({
10710
+ indexName: _mastra_core_storage.TABLE_MESSAGES,
10711
+ schemaName: getSchemaName$3(this.#schema)
10712
+ });
10713
+ try {
10714
+ return await this.#db.client.tx(async (t) => {
10715
+ const thread = await t.oneOrNone(`SELECT * FROM ${threadsTable} WHERE id = $1 FOR UPDATE`, [threadId]);
10716
+ if (!thread) throw new Error(`Thread "${threadId}" not found`);
10717
+ const normalized = {
10718
+ id: thread.id,
10719
+ resourceId: thread.resourceId,
10720
+ title: thread.title,
10721
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
10722
+ createdAt: thread.createdAtZ || thread.createdAt,
10723
+ updatedAt: thread.updatedAtZ || thread.updatedAt
10724
+ };
10725
+ if (thread.resourceId === resourceId) return normalized;
10726
+ await t.none(`UPDATE ${threadsTable} SET "resourceId" = $1, "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id = $2`, [resourceId, threadId]);
10727
+ await t.none(`UPDATE ${messagesTable} SET "resourceId" = $1 WHERE thread_id = $2`, [resourceId, threadId]);
10728
+ return {
10729
+ ...normalized,
10730
+ resourceId,
10731
+ updatedAt: /* @__PURE__ */ new Date()
10732
+ };
10733
+ });
10734
+ } catch (error) {
10735
+ throw new _mastra_core_error.MastraError({
10736
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_THREAD_RESOURCE_ID", "FAILED"),
10737
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
10738
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
10739
+ details: {
10740
+ threadId,
10741
+ resourceId
10742
+ }
10743
+ }, error);
10744
+ }
10745
+ }
10695
10746
  async listThreads(args) {
10696
10747
  const { page = 0, perPage: perPageInput, orderBy, filter } = args;
10697
10748
  try {
@@ -11696,7 +11747,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11696
11747
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
11697
11748
  return updatedResource;
11698
11749
  }
11699
- async cloneThread(args) {
11750
+ async copyThread(args) {
11700
11751
  const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
11701
11752
  const sourceThread = await this.#getThreadById(this.#db.client, { threadId: sourceThreadId });
11702
11753
  if (!sourceThread) throw new _mastra_core_error.MastraError({
@@ -11722,10 +11773,9 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11722
11773
  indexName: _mastra_core_storage.TABLE_MESSAGES,
11723
11774
  schemaName: getSchemaName$3(this.#schema)
11724
11775
  });
11725
- const hydrateMessages = options?.hydrateMessages ?? true;
11726
11776
  try {
11727
11777
  return await this.#db.client.tx(async (t) => {
11728
- let messageQuery = `SELECT ${hydrateMessages ? `id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"` : `id, "createdAt"`}
11778
+ let messageQuery = `SELECT id, "createdAt"
11729
11779
  FROM ${messageTableName} WHERE thread_id = $1`;
11730
11780
  const messageParams = [sourceThreadId];
11731
11781
  let paramIndex = 2;
@@ -11786,54 +11836,23 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11786
11836
  nowStr,
11787
11837
  nowStr
11788
11838
  ]);
11789
- const clonedMessages = [];
11790
11839
  const messageIdMap = {};
11791
11840
  const targetResourceId = resourceId || sourceThread.resourceId;
11792
11841
  for (const sourceMsg of sourceMessages) {
11793
11842
  const newMessageId = crypto.randomUUID();
11794
11843
  messageIdMap[sourceMsg.id] = newMessageId;
11795
- if (!hydrateMessages) {
11796
- const insertResult = await t.query(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11797
- SELECT $1, $2, content, "createdAt", "createdAtZ", role, type, $3
11798
- FROM ${messageTableName} WHERE id = $4`, [
11799
- newMessageId,
11800
- newThreadId,
11801
- targetResourceId,
11802
- sourceMsg.id
11803
- ]);
11804
- if (insertResult.rowCount !== 1) throw new Error(`Failed to clone message ${sourceMsg.id}: expected 1 row copied but got ${insertResult.rowCount}`);
11805
- continue;
11806
- }
11807
- const normalizedMsg = this.normalizeMessageRow(sourceMsg);
11808
- let parsedContent = normalizedMsg.content;
11809
- try {
11810
- parsedContent = JSON.parse(normalizedMsg.content);
11811
- } catch {}
11812
- const createdAt = toUtcISOString(new Date(normalizedMsg.createdAt));
11813
- await t.none(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11814
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
11844
+ const insertResult = await t.query(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
11845
+ SELECT $1, $2, content, "createdAt", "createdAtZ", role, type, $3
11846
+ FROM ${messageTableName} WHERE id = $4`, [
11815
11847
  newMessageId,
11816
11848
  newThreadId,
11817
- typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
11818
- createdAt,
11819
- createdAt,
11820
- normalizedMsg.role,
11821
- normalizedMsg.type || "v2",
11822
- targetResourceId
11849
+ targetResourceId,
11850
+ sourceMsg.id
11823
11851
  ]);
11824
- clonedMessages.push({
11825
- id: newMessageId,
11826
- threadId: newThreadId,
11827
- content: parsedContent,
11828
- role: normalizedMsg.role,
11829
- type: normalizedMsg.type,
11830
- createdAt: new Date(normalizedMsg.createdAt),
11831
- resourceId: targetResourceId
11832
- });
11852
+ if (insertResult.rowCount !== 1) throw new Error(`Failed to copy message ${sourceMsg.id}: expected 1 row copied but got ${insertResult.rowCount}`);
11833
11853
  }
11834
11854
  return {
11835
11855
  thread: newThread,
11836
- clonedMessages,
11837
11856
  messageIdMap
11838
11857
  };
11839
11858
  });
@@ -13188,6 +13207,27 @@ var NotificationsPG = class NotificationsPG extends _mastra_core_storage.Notific
13188
13207
  if (!updated) throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`);
13189
13208
  return updated;
13190
13209
  }
13210
+ async updateNotificationsStatus(input) {
13211
+ const ids = Array.from(new Set(input.ids));
13212
+ if (ids.length === 0) return [];
13213
+ const now = /* @__PURE__ */ new Date();
13214
+ const assignments = {
13215
+ status: input.status,
13216
+ ...statusTimestamp(input.status, now),
13217
+ updatedAt: now
13218
+ };
13219
+ const columns = Object.keys(assignments);
13220
+ const setClause = columns.map((column, index) => `"${(0, _mastra_core_utils.parseSqlIdentifier)(column, "column name")}" = $${index + 1}`).join(", ");
13221
+ const tableName = getTableName$5({
13222
+ indexName: _mastra_core_storage.TABLE_NOTIFICATIONS,
13223
+ schemaName: getSchemaName$5(this.#schema)
13224
+ });
13225
+ return (await this.#db.client.manyOrNone(`UPDATE ${tableName} SET ${setClause} WHERE "threadId" = $${columns.length + 1} AND "id" = ANY($${columns.length + 2}::text[]) RETURNING *`, [
13226
+ ...Object.values(assignments),
13227
+ input.threadId,
13228
+ ids
13229
+ ])).map(rowToNotification);
13230
+ }
13191
13231
  async findCoalescable(input) {
13192
13232
  if (!input.dedupeKey && !input.coalesceKey) return void 0;
13193
13233
  const tableName = getTableName$5({
@@ -17342,6 +17382,13 @@ function collectRelationCollections(predicate, collections = /* @__PURE__ */ new
17342
17382
  else if (predicate.type === "not") collectRelationCollections(predicate.arg, collections);
17343
17383
  return collections;
17344
17384
  }
17385
+ function collectThreadRelationCollections(predicate, collections) {
17386
+ if (!predicate) return collections;
17387
+ if (predicate.type === "relation") collectRelationCollections(predicate.predicate, collections);
17388
+ else if (predicate.type === "boolean") for (const arg of predicate.args) collectThreadRelationCollections(arg, collections);
17389
+ else collectThreadRelationCollections(predicate.arg, collections);
17390
+ return collections;
17391
+ }
17345
17392
  function compilePredicate(predicate, parameterOffset) {
17346
17393
  if (predicate.type === "relation") {
17347
17394
  const compiled = predicate.collection === "feedback" ? compileFeedbackScalarPredicate(predicate.predicate, parameterOffset) : compileScalarPredicate(predicate.predicate, predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS, parameterOffset);
@@ -17376,12 +17423,41 @@ function compilePredicate(predicate, parameterOffset) {
17376
17423
  }
17377
17424
  return compileScalarPredicate(predicate, TRACE_FIELDS, parameterOffset, true);
17378
17425
  }
17379
- function compilePostgresTraceQuery(schema, plan) {
17426
+ function compileThreadPredicate(predicate, parameterOffset) {
17427
+ if (predicate.type === "relation") {
17428
+ const compiled = compilePredicate(predicate.predicate, parameterOffset);
17429
+ const existence = `EXISTS (
17430
+ SELECT 1 FROM eligible_roots r
17431
+ WHERE r."threadId" = t."threadId"
17432
+ AND (${compiled.sql})
17433
+ )`;
17434
+ return {
17435
+ sql: predicate.quantifier === "some" ? existence : `NOT ${existence}`,
17436
+ values: compiled.values
17437
+ };
17438
+ }
17439
+ if (predicate.type === "boolean") {
17440
+ const values = [];
17441
+ return {
17442
+ sql: predicate.args.map((arg) => {
17443
+ const compiled = compileThreadPredicate(arg, parameterOffset + values.length);
17444
+ values.push(...compiled.values);
17445
+ return `(${compiled.sql})`;
17446
+ }).join(predicate.operator === "and" ? " AND " : " OR "),
17447
+ values
17448
+ };
17449
+ }
17450
+ const compiled = compileThreadPredicate(predicate.arg, parameterOffset);
17451
+ return {
17452
+ sql: `NOT (${compiled.sql})`,
17453
+ values: compiled.values
17454
+ };
17455
+ }
17456
+ function compilePostgresTraceScope(schema, selection, relationCollections) {
17380
17457
  const spanTable = qualifiedTable(schema, TABLE_SPAN_EVENTS);
17381
17458
  const scoreTable = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17382
17459
  const feedbackTable = qualifiedTable(schema, TABLE_FEEDBACK_EVENTS);
17383
- const values = [plan.timeRange.from, plan.timeRange.to];
17384
- const relationCollections = collectRelationCollections(plan.where);
17460
+ const values = [selection.timeRange.from, selection.timeRange.to];
17385
17461
  const ctes = [`root_scope AS MATERIALIZED (
17386
17462
  SELECT *
17387
17463
  FROM ${spanTable} r
@@ -17456,6 +17532,13 @@ function compilePostgresTraceQuery(schema, plan) {
17456
17532
  AND s."traceId" IN (SELECT "traceId" FROM root_scope)
17457
17533
  AND ${latestFeedbackPredicate(feedbackTable)}
17458
17534
  )`);
17535
+ return {
17536
+ ctes,
17537
+ values
17538
+ };
17539
+ }
17540
+ function compilePostgresTraceQuery(schema, plan) {
17541
+ const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
17459
17542
  let predicateSql = "TRUE";
17460
17543
  if (plan.where) {
17461
17544
  const predicate = compilePredicate(plan.where, values.length + 1);
@@ -17503,6 +17586,51 @@ LIMIT $${values.length}`,
17503
17586
  values
17504
17587
  };
17505
17588
  }
17589
+ function compilePostgresThreadQuery(schema, plan) {
17590
+ const relationCollections = collectRelationCollections(plan.traces.where);
17591
+ collectThreadRelationCollections(plan.where, relationCollections);
17592
+ const { ctes, values } = compilePostgresTraceScope(schema, plan.traces, relationCollections);
17593
+ let eligibilitySql = "TRUE";
17594
+ if (plan.traces.where) {
17595
+ const eligibility = compilePredicate(plan.traces.where, values.length + 1);
17596
+ eligibilitySql = eligibility.sql;
17597
+ values.push(...eligibility.values);
17598
+ }
17599
+ ctes.push(`eligible_roots AS MATERIALIZED (
17600
+ SELECT *
17601
+ FROM root_scope r
17602
+ WHERE ${eligibilitySql}
17603
+ )`);
17604
+ ctes.push(`thread_ids AS (
17605
+ SELECT "threadId" COLLATE "C" AS "threadId"
17606
+ FROM eligible_roots
17607
+ WHERE "threadId" IS NOT NULL
17608
+ GROUP BY "threadId" COLLATE "C"
17609
+ )`);
17610
+ let threadPredicateSql = "TRUE";
17611
+ if (plan.where) {
17612
+ const predicate = compileThreadPredicate(plan.where, values.length + 1);
17613
+ threadPredicateSql = predicate.sql;
17614
+ values.push(...predicate.values);
17615
+ }
17616
+ ctes.push(`qualified_threads AS (
17617
+ SELECT t."threadId"
17618
+ FROM thread_ids t
17619
+ WHERE ${threadPredicateSql}
17620
+ )`);
17621
+ const pageCondition = plan.cursor ? `WHERE "threadId" > $${values.length + 1}` : "";
17622
+ if (plan.cursor) values.push(plan.cursor.threadId);
17623
+ values.push(plan.limit + 1);
17624
+ return {
17625
+ text: `WITH ${ctes.join(",\n")}
17626
+ SELECT "threadId"
17627
+ FROM qualified_threads
17628
+ ${pageCondition}
17629
+ ORDER BY "threadId" ASC
17630
+ LIMIT $${values.length}`,
17631
+ values
17632
+ };
17633
+ }
17506
17634
  function asIsoTimestamp$1(value) {
17507
17635
  if (value === null || value === void 0) throw new Error("Trace query returned a null timestamp");
17508
17636
  return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
@@ -17561,6 +17689,19 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17561
17689
  }) : null }
17562
17690
  });
17563
17691
  }
17692
+ async function queryThreads(client, schema, plan, timeoutMs) {
17693
+ const query = compilePostgresThreadQuery(schema, plan);
17694
+ const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17695
+ const threads = rows.slice(0, plan.limit).map((row) => ({ threadId: String(row.threadId) }));
17696
+ const last = threads.at(-1);
17697
+ return _mastra_core_storage.queryThreadsResultSchema.parse({
17698
+ threads,
17699
+ page: { next: rows.length > plan.limit && last ? _mastra_core_storage.encodeTraceQueryCursor(plan, {
17700
+ result: "threads",
17701
+ threadId: last.threadId
17702
+ }) : null }
17703
+ });
17704
+ }
17564
17705
  //#endregion
17565
17706
  //#region src/storage/domains/observability/v-next/traces.ts
17566
17707
  /**
@@ -18456,13 +18597,15 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18456
18597
  if (!deltaPollingFeatureEnabled()) return [
18457
18598
  "metrics",
18458
18599
  "logs",
18459
- "trace-query"
18600
+ "trace-query",
18601
+ "thread-query"
18460
18602
  ];
18461
18603
  return [
18462
18604
  "metrics",
18463
18605
  "logs",
18464
18606
  "delta-polling",
18465
- "trace-query"
18607
+ "trace-query",
18608
+ "thread-query"
18466
18609
  ];
18467
18610
  }
18468
18611
  async #run(op, fn, details) {
@@ -18508,6 +18651,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
18508
18651
  async queryTraces(plan) {
18509
18652
  return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18510
18653
  }
18654
+ async queryThreads(plan) {
18655
+ return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
18656
+ }
18511
18657
  async listBranches(args) {
18512
18658
  return this.#run("LIST_BRANCHES", () => listBranches(this.#readClient, this.#schema, args));
18513
18659
  }