@mastra/pg 1.26.0-alpha.1 → 1.26.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.cjs CHANGED
@@ -4927,137 +4927,31 @@ var AgentsPG = class AgentsPG extends _mastra_core_storage.AgentsStorage {
4927
4927
  };
4928
4928
  //#endregion
4929
4929
  //#region src/storage/retention.ts
4930
- const DEFAULT_BATCH_SIZE = 1e3;
4931
- async function sleep(ms, signal) {
4932
- if (ms <= 0 || signal?.aborted) return;
4933
- await new Promise((resolve) => {
4934
- const timer = setTimeout(() => {
4935
- signal?.removeEventListener("abort", onAbort);
4936
- resolve();
4937
- }, ms);
4938
- const onAbort = () => {
4939
- clearTimeout(timer);
4940
- resolve();
4941
- };
4942
- signal?.addEventListener("abort", onAbort, { once: true });
4943
- });
4944
- }
4945
- /**
4946
- * Convert a policy's `maxAge` into a cutoff bound matching the anchor's storage
4947
- * type: a `Date` for `timestamptz` columns (pg compares timezone-aware), or a
4948
- * raw millisecond number for `bigint` epoch-ms columns.
4949
- */
4950
4930
  function cutoffFor(policy, anchorType, now = Date.now()) {
4951
- const cutoffMs = now - (0, _mastra_core_storage.parseDuration)(policy.maxAge);
4931
+ const cutoffMs = (0, _mastra_core_storage.retentionCutoffMs)(policy, now);
4952
4932
  return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
4953
4933
  }
4954
- /**
4955
- * Run the bounded/cancellable batched-delete loop for a single logical target,
4956
- * delegating the actual delete of up to `limit` rows to `deleteBatch`. Returns
4957
- * `{ deleted, done }`; `done: false` means the loop stopped on a bound or the
4958
- * abort signal and eligible rows may remain.
4959
- */
4960
- async function runBatchedDelete({ deleteBatch, batchSize, options }) {
4961
- if (!Number.isSafeInteger(batchSize) || batchSize <= 0) throw new Error(`retention batchSize must be a positive integer; received ${batchSize}`);
4962
- let deleted = 0;
4963
- let batches = 0;
4964
- while (true) {
4965
- if (options?.signal?.aborted) return {
4966
- deleted,
4967
- done: false
4968
- };
4969
- if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return {
4970
- deleted,
4971
- done: false
4972
- };
4973
- let limit = batchSize;
4974
- if (options?.maxRows !== void 0) {
4975
- const remaining = options.maxRows - deleted;
4976
- if (remaining <= 0) return {
4977
- deleted,
4978
- done: false
4979
- };
4980
- limit = Math.min(limit, remaining);
4981
- }
4982
- const affected = await deleteBatch(limit);
4983
- deleted += affected;
4984
- batches += 1;
4985
- if (affected < limit) return {
4986
- deleted,
4987
- done: true
4988
- };
4989
- if (options?.pauseMs) await sleep(options.pauseMs, options.signal);
4990
- }
4991
- }
4992
- /**
4993
- * Runs the bounded, batched, cancellable delete loop for a set of tables in the
4994
- * given order (callers pass children before parents for cascade-safe pruning),
4995
- * and returns one {@link PruneResult} per table.
4996
- *
4997
- * The loop:
4998
- * - deletes in chunks of `batchSize` (default 1000), each its own statement;
4999
- * - stops a table's loop when a batch deletes fewer rows than requested (drained),
5000
- * or when `maxBatches`/`maxRows` is hit, or the `signal` aborts — the latter
5001
- * three leave `done: false` so the caller can resume;
5002
- * - pauses `pauseMs` between batches when set, to avoid starving live traffic.
5003
- *
5004
- * `prune()` only deletes rows; it never reclaims disk. PostgreSQL reuses freed
5005
- * space (dead tuples) via autovacuum on subsequent writes, so tables stop
5006
- * growing. Returning disk to the OS (e.g. `VACUUM FULL`) is left to the operator.
5007
- */
5008
- async function runPrune({ db, domain, targets, options }) {
5009
- const results = [];
5010
- const now = Date.now();
5011
- for (const target of targets) {
5012
- if (options?.signal?.aborted) {
5013
- results.push({
5014
- domain,
5015
- table: target.table,
5016
- deleted: 0,
5017
- done: false
5018
- });
5019
- continue;
5020
- }
5021
- const cutoff = cutoffFor(target.policy, target.anchorType, now);
5022
- const { deleted, done } = await runBatchedDelete({
5023
- deleteBatch: (limit) => db.pruneBatch({
5024
- tableName: target.table,
5025
- column: target.column,
5026
- cutoff,
5027
- limit
5028
- }),
5029
- batchSize: target.policy.batchSize ?? DEFAULT_BATCH_SIZE,
5030
- options
5031
- });
5032
- results.push({
5033
- domain,
5034
- table: target.table,
5035
- deleted,
5036
- done
5037
- });
5038
- }
5039
- return results;
4934
+ const runBatchedDelete = _mastra_core_storage.runRetentionBatches;
4935
+ function runPrune({ db, domain, targets, options }) {
4936
+ return (0, _mastra_core_storage.executeRetentionPrune)({
4937
+ domain,
4938
+ targets,
4939
+ options,
4940
+ cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
4941
+ deleteBatch: (target, cutoff, limit) => db.pruneBatch({
4942
+ tableName: target.table,
4943
+ column: target.column,
4944
+ cutoff,
4945
+ limit
4946
+ })
4947
+ });
5040
4948
  }
5041
- /**
5042
- * Resolve a domain's `{ tableKey: policy }` map plus its descriptor into an
5043
- * ordered list of {@link PruneTarget}s. `order` lists table keys children-first
5044
- * so cascade-dependent rows are removed before their parents. Table keys not in
5045
- * `policies` are skipped (unset = keep forever).
5046
- */
5047
4949
  function resolveTargets({ policies, descriptor, order }) {
5048
- const targets = [];
5049
- for (const key of order) {
5050
- const policy = policies[key];
5051
- const entry = descriptor[key];
5052
- if (!policy || !entry) continue;
5053
- targets.push({
5054
- table: entry.table,
5055
- column: entry.column,
5056
- anchorType: entry.anchorType ?? "timestamp",
5057
- policy
5058
- });
5059
- }
5060
- return targets;
4950
+ return (0, _mastra_core_storage.resolveRetentionTargets)({
4951
+ policies,
4952
+ descriptor,
4953
+ order
4954
+ });
5061
4955
  }
5062
4956
  //#endregion
5063
4957
  //#region src/storage/domains/background-tasks/index.ts
@@ -10888,7 +10782,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
10888
10782
  hasMore: false
10889
10783
  };
10890
10784
  const limitValue = perPageInput === false ? total : perPage;
10891
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
10785
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction}, "id" ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
10892
10786
  return {
10893
10787
  threads: (await this.#db.readClient.manyOrNone(dataQuery, [
10894
10788
  ...queryParams,
@@ -11081,10 +10975,11 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11081
10975
  return messages.sort((a, b) => {
11082
10976
  const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
11083
10977
  const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
11084
- if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
10978
+ const idOrder = direction === "ASC" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id);
10979
+ if (aValue == null && bValue == null) return idOrder;
11085
10980
  if (aValue == null) return 1;
11086
10981
  if (bValue == null) return -1;
11087
- if (aValue === bValue) return a.id.localeCompare(b.id);
10982
+ if (aValue === bValue) return idOrder;
11088
10983
  if (typeof aValue === "number" && typeof bValue === "number") return direction === "ASC" ? aValue - bValue : bValue - aValue;
11089
10984
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
11090
10985
  });
@@ -11287,7 +11182,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11287
11182
  const metadataFilter = (0, _mastra_core_storage.validateStorageMetadataFilter)(filter?.metadata);
11288
11183
  try {
11289
11184
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
11290
- const orderByStatement = `ORDER BY "${field}" ${direction}`;
11185
+ const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
11291
11186
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
11292
11187
  const tableName = getTableName$3({
11293
11188
  indexName: _mastra_core_storage.TABLE_MESSAGES,
@@ -11442,7 +11337,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11442
11337
  const metadataFilter = (0, _mastra_core_storage.validateStorageMetadataFilter)(filter?.metadata);
11443
11338
  try {
11444
11339
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
11445
- const orderByStatement = `ORDER BY "${field}" ${direction}`;
11340
+ const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
11446
11341
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
11447
11342
  const tableName = getTableName$3({
11448
11343
  indexName: _mastra_core_storage.TABLE_MESSAGES,
@@ -14937,6 +14832,11 @@ function tableIndexes() {
14937
14832
  columns: "(\"tags\")",
14938
14833
  using: "gin"
14939
14834
  },
14835
+ {
14836
+ name: "mastra_score_events_scoreid_cursor_idx",
14837
+ table: TABLE_SCORE_EVENTS,
14838
+ columns: "(\"scoreId\", \"cursorId\" DESC)"
14839
+ },
14940
14840
  {
14941
14841
  name: "mastra_score_events_cursor_idx",
14942
14842
  table: TABLE_SCORE_EVENTS,
@@ -17069,13 +16969,32 @@ function pushScoreIdentity(acc, scorerId, scoreSource) {
17069
16969
  acc.params.push(scoreSource);
17070
16970
  }
17071
16971
  }
16972
+ function scoreRewriteConflict(row) {
16973
+ return `ON CONFLICT ("scoreId", "timestamp") DO UPDATE SET ${[
16974
+ ...Object.keys(row).filter((column) => column !== "scoreId" && column !== "timestamp").map((column) => `"${column}" = EXCLUDED."${column}"`),
16975
+ "\"cursorId\" = EXCLUDED.\"cursorId\"",
16976
+ "\"xactId\" = EXCLUDED.\"xactId\""
16977
+ ].join(", ")}`;
16978
+ }
16979
+ function collapseExactScoreConflicts(rows) {
16980
+ const records = /* @__PURE__ */ new Map();
16981
+ for (const row of rows) {
16982
+ const timestamp = new Date(row.timestamp).toISOString();
16983
+ const key = `${String(row.scoreId)}\u0000${timestamp}`;
16984
+ records.delete(key);
16985
+ records.set(key, row);
16986
+ }
16987
+ return [...records.values()];
16988
+ }
17072
16989
  async function createScore(client, schema, args) {
17073
- const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [scoreRecordToRow(args.score)]);
16990
+ const row = scoreRecordToRow(args.score);
16991
+ const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [row], scoreRewriteConflict(row));
17074
16992
  if (insert) await client.query(insert.text, insert.values);
17075
16993
  }
17076
16994
  async function batchCreateScores(client, schema, args) {
17077
16995
  if (args.scores.length === 0) return;
17078
- const insert = buildInsert(schema, TABLE_SCORE_EVENTS, args.scores.map(scoreRecordToRow));
16996
+ const rows = collapseExactScoreConflicts(args.scores.map(scoreRecordToRow));
16997
+ const insert = buildInsert(schema, TABLE_SCORE_EVENTS, rows, scoreRewriteConflict(rows[0]));
17079
16998
  if (insert) await client.query(insert.text, insert.values);
17080
16999
  }
17081
17000
  /**
@@ -17098,6 +17017,16 @@ async function deleteScores(client, schema, args) {
17098
17017
  }
17099
17018
  await client.query(`DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`, values);
17100
17019
  }
17020
+ function latestScorePredicate(table, alias = "s") {
17021
+ return `NOT EXISTS (
17022
+ SELECT 1 FROM ${table} newer
17023
+ WHERE newer."scoreId" = ${alias}."scoreId"
17024
+ AND newer."cursorId" > ${alias}."cursorId"
17025
+ )`;
17026
+ }
17027
+ function applyLatestScorePredicate(acc, table) {
17028
+ acc.conditions.push(latestScorePredicate(table));
17029
+ }
17101
17030
  async function listScores(client, schema, args) {
17102
17031
  const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listScoresArgsSchema.parse(args);
17103
17032
  const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
@@ -17108,28 +17037,50 @@ async function listScores(client, schema, args) {
17108
17037
  return listScoresPage(client, table, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
17109
17038
  }
17110
17039
  async function getScoreById(client, schema, scoreId) {
17040
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17111
17041
  const row = await client.oneOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
17112
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17042
+ FROM ${table}
17113
17043
  WHERE "scoreId" = $1
17114
- ORDER BY "timestamp" DESC
17044
+ ORDER BY "cursorId" DESC
17115
17045
  LIMIT 1`, [scoreId]);
17116
17046
  return row ? rowToScoreRecord(row) : null;
17117
17047
  }
17118
17048
  async function listScoresPage(client, table, filters, page, perPage, orderField, orderDir) {
17119
- return listSignalPage({
17049
+ const acc = newFilterAccumulator();
17050
+ applyScoreFilters(acc, filters);
17051
+ applyLatestScorePredicate(acc, table);
17052
+ const whereClause = whereOrEmpty(acc);
17053
+ const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${table} s ${whereClause}`, acc.params);
17054
+ const total = Number(countRow?.count ?? 0);
17055
+ let scores = [];
17056
+ if (total > 0) {
17057
+ const safeOrderField = (0, _mastra_core_utils.parseSqlIdentifier)(orderField, "order field");
17058
+ scores = (await client.manyOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
17059
+ FROM ${table} s
17060
+ ${whereClause}
17061
+ ORDER BY "${safeOrderField}" ${orderDir}, "cursorId" ${orderDir}
17062
+ LIMIT $${acc.next++} OFFSET $${acc.next++}`, [
17063
+ ...acc.params,
17064
+ perPage,
17065
+ page * perPage
17066
+ ])).map(rowToScoreRecord);
17067
+ }
17068
+ const deltaCursor = deltaPollingFeatureEnabled() ? await readSignalStreamHeadCursor({
17120
17069
  client,
17121
17070
  table,
17122
17071
  filters,
17123
- page,
17124
- perPage,
17125
- orderField,
17126
- orderDir,
17127
- includeDeltaCursor: deltaPollingFeatureEnabled(),
17128
- selectColumns: SCORE_SELECT_COLUMNS,
17129
- responseKey: "scores",
17130
- applyFilters: applyScoreFilters,
17131
- mapRow: rowToScoreRecord
17132
- });
17072
+ applyFilters: applyScoreFilters
17073
+ }) : void 0;
17074
+ return {
17075
+ scores,
17076
+ pagination: {
17077
+ total,
17078
+ page,
17079
+ perPage,
17080
+ hasMore: (page + 1) * perPage < total
17081
+ },
17082
+ ...deltaCursor !== void 0 ? { deltaCursor } : {}
17083
+ };
17133
17084
  }
17134
17085
  async function listScoresDelta(client, table, filters, after, limit) {
17135
17086
  return listSignalDelta({
@@ -17145,12 +17096,14 @@ async function listScoresDelta(client, table, filters, after, limit) {
17145
17096
  });
17146
17097
  }
17147
17098
  async function runScoreAggregateQuery(client, schema, args, filters) {
17099
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17148
17100
  const acc = newFilterAccumulator();
17149
17101
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17150
17102
  applyScoreFilters(acc, filters);
17103
+ applyLatestScorePredicate(acc, table);
17151
17104
  const sql = `
17152
17105
  SELECT ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17153
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17106
+ FROM ${table} s
17154
17107
  ${whereOrEmpty(acc)}
17155
17108
  `;
17156
17109
  const row = await client.oneOrNone(sql, acc.params);
@@ -17182,10 +17135,12 @@ async function getScoreBreakdown(client, schema, args) {
17182
17135
  });
17183
17136
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17184
17137
  applyScoreFilters(acc, args.filters);
17138
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17139
+ applyLatestScorePredicate(acc, table);
17185
17140
  const sql = `
17186
17141
  SELECT ${resolved.map((e) => e.selectSql).join(", ")},
17187
17142
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17188
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17143
+ FROM ${table} s
17189
17144
  ${whereOrEmpty(acc)}
17190
17145
  GROUP BY ${resolved.map((e) => e.alias).join(", ")}
17191
17146
  ORDER BY "value" DESC NULLS LAST
@@ -17205,11 +17160,13 @@ async function getScoreTimeSeries(client, schema, args) {
17205
17160
  });
17206
17161
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17207
17162
  applyScoreFilters(acc, args.filters);
17163
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17164
+ applyLatestScorePredicate(acc, table);
17208
17165
  const sql = `
17209
17166
  SELECT ${bucket} AS bucket,
17210
17167
  ${resolved.map((e) => e.selectSql).join(", ")},
17211
17168
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17212
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17169
+ FROM ${table} s
17213
17170
  ${whereOrEmpty(acc)}
17214
17171
  GROUP BY bucket, ${resolved.map((e) => e.alias).join(", ")}
17215
17172
  ORDER BY bucket
@@ -17227,10 +17184,12 @@ async function getScoreTimeSeries(client, schema, args) {
17227
17184
  const acc = newFilterAccumulator();
17228
17185
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17229
17186
  applyScoreFilters(acc, args.filters);
17187
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17188
+ applyLatestScorePredicate(acc, table);
17230
17189
  const sql = `
17231
17190
  SELECT ${bucket} AS bucket,
17232
17191
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17233
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17192
+ FROM ${table} s
17234
17193
  ${whereOrEmpty(acc)}
17235
17194
  GROUP BY bucket
17236
17195
  ORDER BY bucket
@@ -17250,9 +17209,11 @@ async function getScorePercentiles(client, schema, args) {
17250
17209
  const acc = newFilterAccumulator();
17251
17210
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17252
17211
  applyScoreFilters(acc, args.filters);
17212
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17213
+ applyLatestScorePredicate(acc, table);
17253
17214
  const sql = `
17254
17215
  SELECT ${bucket} AS bucket, ${percentileSelectSql(args.percentiles, "\"score\"")}
17255
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17216
+ FROM ${table} s
17256
17217
  ${whereOrEmpty(acc)}
17257
17218
  GROUP BY bucket
17258
17219
  ORDER BY bucket
@@ -17315,6 +17276,11 @@ const FEEDBACK_FIELDS = {
17315
17276
  const TRACE_SELECT = `
17316
17277
  r."traceId" AS "traceId",
17317
17278
  r."spanId" AS "rootSpanId",
17279
+ r."name" AS "name",
17280
+ r."entityId" AS "entityId",
17281
+ r."parentSpanId" AS "parentSpanId",
17282
+ r."metadata" AS "metadata",
17283
+ r."input" AS "input",
17318
17284
  r."threadId" AS "threadId",
17319
17285
  r."resourceId" AS "resourceId",
17320
17286
  r."startedAt" AS "startedAt",
@@ -17446,13 +17412,6 @@ function latestSpanPredicate$1(spanTable) {
17446
17412
  AND (newer."isPending" < s."isPending" OR (newer."isPending" = s."isPending" AND newer."cursorId" > s."cursorId"))
17447
17413
  )`;
17448
17414
  }
17449
- function latestScorePredicate(scoreTable) {
17450
- return `NOT EXISTS (
17451
- SELECT 1 FROM ${scoreTable} newer
17452
- WHERE newer."scoreId" = s."scoreId"
17453
- AND newer."cursorId" > s."cursorId"
17454
- )`;
17455
- }
17456
17415
  function latestFeedbackPredicate(feedbackTable) {
17457
17416
  return `NOT EXISTS (
17458
17417
  SELECT 1 FROM ${feedbackTable} newer
@@ -17622,7 +17581,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections) {
17622
17581
  values
17623
17582
  };
17624
17583
  }
17625
- function compilePostgresTraceQuery(schema, plan) {
17584
+ function compilePostgresTraceQuery(schema, plan, mode = "data") {
17626
17585
  const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
17627
17586
  let predicateSql = "TRUE";
17628
17587
  if (plan.where) {
@@ -17636,6 +17595,12 @@ function compilePostgresTraceQuery(schema, plan) {
17636
17595
  WHERE ${predicateSql}
17637
17596
  )`);
17638
17597
  const candidates = `WITH ${ctes.join(",\n")}`;
17598
+ if (plan.paginationMode === "page" && mode === "count") return {
17599
+ text: `${candidates}
17600
+ SELECT COUNT(*)::text AS count
17601
+ FROM candidates`,
17602
+ values
17603
+ };
17639
17604
  if (plan.result === "groups") {
17640
17605
  const pageCondition = plan.cursor ? `AND "threadId" > $${values.length + 1}` : "";
17641
17606
  if (plan.cursor) values.push(plan.cursor.threadId);
@@ -17653,6 +17618,17 @@ LIMIT $${values.length}`,
17653
17618
  }
17654
17619
  const orderField = plan.orderBy.field === "startedAt" ? "\"startedAt\"" : "\"endedAt\"";
17655
17620
  const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
17621
+ if (plan.paginationMode === "page") {
17622
+ values.push(plan.perPage, plan.page * plan.perPage);
17623
+ return {
17624
+ text: `${candidates}
17625
+ SELECT *
17626
+ FROM candidates
17627
+ ORDER BY ${orderField} ${direction}, "traceId" ASC
17628
+ LIMIT $${values.length - 1} OFFSET $${values.length}`,
17629
+ values
17630
+ };
17631
+ }
17656
17632
  let pageCondition = "";
17657
17633
  if (plan.cursor) {
17658
17634
  const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
@@ -17794,15 +17770,22 @@ function isPostgresStatementTimeout(error) {
17794
17770
  const candidate = error;
17795
17771
  return candidate.code === "57014" && String(candidate.message ?? "").includes("statement timeout");
17796
17772
  }
17797
- async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
17773
+ function isPostgresResourceLimit(error) {
17774
+ if (!error || typeof error !== "object") return false;
17775
+ const candidate = error;
17776
+ return candidate.code === "53200" || candidate.code === "53400";
17777
+ }
17778
+ async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute, options = {}) {
17798
17779
  const resolvedTimeoutMs = _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
17799
17780
  try {
17800
17781
  return await client.tx(async (transaction) => {
17782
+ if (options.repeatableRead) await transaction.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
17801
17783
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${resolvedTimeoutMs}ms`]);
17802
17784
  return execute(transaction);
17803
17785
  });
17804
17786
  } catch (error) {
17805
17787
  if (isPostgresStatementTimeout(error)) throw new _mastra_core_storage.TraceQueryExecutionError();
17788
+ if (isPostgresResourceLimit(error)) throw new _mastra_core_storage.TraceQueryResourceLimitError();
17806
17789
  throw error;
17807
17790
  }
17808
17791
  }
@@ -17830,6 +17813,50 @@ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
17830
17813
  });
17831
17814
  }
17832
17815
  async function queryTraces(client, schema, plan, timeoutMs) {
17816
+ if (plan.paginationMode === "page") {
17817
+ const resolvedTimeoutMs = _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
17818
+ const deadline = performance.now() + resolvedTimeoutMs;
17819
+ const countQuery = compilePostgresTraceQuery(schema, plan, "count");
17820
+ const dataQuery = compilePostgresTraceQuery(schema, plan);
17821
+ const { total, rows } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
17822
+ const countRows = await transaction.any(countQuery.text, countQuery.values);
17823
+ const remainingTimeoutMs = Math.floor(deadline - performance.now());
17824
+ if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
17825
+ await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
17826
+ const rows = await transaction.any(dataQuery.text, dataQuery.values);
17827
+ return {
17828
+ total: Number(countRows[0]?.count ?? 0),
17829
+ rows
17830
+ };
17831
+ }, { repeatableRead: true });
17832
+ const traces = rows.map((row) => ({
17833
+ traceId: String(row.traceId),
17834
+ rootSpanId: String(row.rootSpanId),
17835
+ name: row.name,
17836
+ entityId: row.entityId ?? null,
17837
+ parentSpanId: row.parentSpanId ?? null,
17838
+ createdAt: asIsoTimestamp$1(row.startedAt),
17839
+ metadata: row.metadata ?? null,
17840
+ inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
17841
+ threadId: row.threadId == null ? null : String(row.threadId),
17842
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
17843
+ startedAt: asIsoTimestamp$1(row.startedAt),
17844
+ endedAt: asIsoTimestamp$1(row.endedAt),
17845
+ entityName: row.entityName == null ? null : String(row.entityName),
17846
+ entityType: row.entityType == null ? null : String(row.entityType),
17847
+ environment: row.environment == null ? null : String(row.environment),
17848
+ status: row.status
17849
+ }));
17850
+ return _mastra_core_storage.traceQueryResponseSchema.parse({
17851
+ traces,
17852
+ pagination: {
17853
+ total,
17854
+ page: plan.page,
17855
+ perPage: plan.perPage,
17856
+ hasMore: (plan.page + 1) * plan.perPage < total
17857
+ }
17858
+ });
17859
+ }
17833
17860
  const query = compilePostgresTraceQuery(schema, plan);
17834
17861
  const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17835
17862
  const visibleRows = rows.slice(0, plan.limit);
@@ -17847,6 +17874,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17847
17874
  const traces = visibleRows.map((row) => ({
17848
17875
  traceId: String(row.traceId),
17849
17876
  rootSpanId: String(row.rootSpanId),
17877
+ name: row.name,
17878
+ entityId: row.entityId ?? null,
17879
+ parentSpanId: row.parentSpanId ?? null,
17880
+ createdAt: asIsoTimestamp$1(row.startedAt),
17881
+ metadata: row.metadata ?? null,
17882
+ inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
17850
17883
  threadId: row.threadId == null ? null : String(row.threadId),
17851
17884
  resourceId: row.resourceId == null ? null : String(row.resourceId),
17852
17885
  startedAt: asIsoTimestamp$1(row.startedAt),
@@ -18594,7 +18627,7 @@ async function dangerouslyClearTracing(client, schema) {
18594
18627
  * Use it through `MastraCompositeStore` with a dedicated Postgres connection.
18595
18628
  */
18596
18629
  function wrapError(op, error, details) {
18597
- if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError) throw error;
18630
+ if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
18598
18631
  throw new _mastra_core_error.MastraError({
18599
18632
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", op, "FAILED"),
18600
18633
  domain: _mastra_core_error.ErrorDomain.STORAGE,