@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "crypto";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
3
  import * as coreStorage from "@mastra/core/storage";
4
- import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_CURSORS_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertKnowledgeCeilingRaised, assertKnowledgeScopeWithinCeiling, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
+ import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_CURSORS_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertKnowledgeCeilingRaised, assertKnowledgeScopeWithinCeiling, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveRetentionTargets, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
5
5
  import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
6
6
  import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
7
7
  import { Mutex } from "async-mutex";
@@ -4903,137 +4903,31 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4903
4903
  };
4904
4904
  //#endregion
4905
4905
  //#region src/storage/retention.ts
4906
- const DEFAULT_BATCH_SIZE = 1e3;
4907
- async function sleep(ms, signal) {
4908
- if (ms <= 0 || signal?.aborted) return;
4909
- await new Promise((resolve) => {
4910
- const timer = setTimeout(() => {
4911
- signal?.removeEventListener("abort", onAbort);
4912
- resolve();
4913
- }, ms);
4914
- const onAbort = () => {
4915
- clearTimeout(timer);
4916
- resolve();
4917
- };
4918
- signal?.addEventListener("abort", onAbort, { once: true });
4919
- });
4920
- }
4921
- /**
4922
- * Convert a policy's `maxAge` into a cutoff bound matching the anchor's storage
4923
- * type: a `Date` for `timestamptz` columns (pg compares timezone-aware), or a
4924
- * raw millisecond number for `bigint` epoch-ms columns.
4925
- */
4926
4906
  function cutoffFor(policy, anchorType, now = Date.now()) {
4927
- const cutoffMs = now - parseDuration(policy.maxAge);
4907
+ const cutoffMs = retentionCutoffMs(policy, now);
4928
4908
  return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
4929
4909
  }
4930
- /**
4931
- * Run the bounded/cancellable batched-delete loop for a single logical target,
4932
- * delegating the actual delete of up to `limit` rows to `deleteBatch`. Returns
4933
- * `{ deleted, done }`; `done: false` means the loop stopped on a bound or the
4934
- * abort signal and eligible rows may remain.
4935
- */
4936
- async function runBatchedDelete({ deleteBatch, batchSize, options }) {
4937
- if (!Number.isSafeInteger(batchSize) || batchSize <= 0) throw new Error(`retention batchSize must be a positive integer; received ${batchSize}`);
4938
- let deleted = 0;
4939
- let batches = 0;
4940
- while (true) {
4941
- if (options?.signal?.aborted) return {
4942
- deleted,
4943
- done: false
4944
- };
4945
- if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return {
4946
- deleted,
4947
- done: false
4948
- };
4949
- let limit = batchSize;
4950
- if (options?.maxRows !== void 0) {
4951
- const remaining = options.maxRows - deleted;
4952
- if (remaining <= 0) return {
4953
- deleted,
4954
- done: false
4955
- };
4956
- limit = Math.min(limit, remaining);
4957
- }
4958
- const affected = await deleteBatch(limit);
4959
- deleted += affected;
4960
- batches += 1;
4961
- if (affected < limit) return {
4962
- deleted,
4963
- done: true
4964
- };
4965
- if (options?.pauseMs) await sleep(options.pauseMs, options.signal);
4966
- }
4967
- }
4968
- /**
4969
- * Runs the bounded, batched, cancellable delete loop for a set of tables in the
4970
- * given order (callers pass children before parents for cascade-safe pruning),
4971
- * and returns one {@link PruneResult} per table.
4972
- *
4973
- * The loop:
4974
- * - deletes in chunks of `batchSize` (default 1000), each its own statement;
4975
- * - stops a table's loop when a batch deletes fewer rows than requested (drained),
4976
- * or when `maxBatches`/`maxRows` is hit, or the `signal` aborts — the latter
4977
- * three leave `done: false` so the caller can resume;
4978
- * - pauses `pauseMs` between batches when set, to avoid starving live traffic.
4979
- *
4980
- * `prune()` only deletes rows; it never reclaims disk. PostgreSQL reuses freed
4981
- * space (dead tuples) via autovacuum on subsequent writes, so tables stop
4982
- * growing. Returning disk to the OS (e.g. `VACUUM FULL`) is left to the operator.
4983
- */
4984
- async function runPrune({ db, domain, targets, options }) {
4985
- const results = [];
4986
- const now = Date.now();
4987
- for (const target of targets) {
4988
- if (options?.signal?.aborted) {
4989
- results.push({
4990
- domain,
4991
- table: target.table,
4992
- deleted: 0,
4993
- done: false
4994
- });
4995
- continue;
4996
- }
4997
- const cutoff = cutoffFor(target.policy, target.anchorType, now);
4998
- const { deleted, done } = await runBatchedDelete({
4999
- deleteBatch: (limit) => db.pruneBatch({
5000
- tableName: target.table,
5001
- column: target.column,
5002
- cutoff,
5003
- limit
5004
- }),
5005
- batchSize: target.policy.batchSize ?? DEFAULT_BATCH_SIZE,
5006
- options
5007
- });
5008
- results.push({
5009
- domain,
5010
- table: target.table,
5011
- deleted,
5012
- done
5013
- });
5014
- }
5015
- return results;
4910
+ const runBatchedDelete = runRetentionBatches;
4911
+ function runPrune({ db, domain, targets, options }) {
4912
+ return executeRetentionPrune({
4913
+ domain,
4914
+ targets,
4915
+ options,
4916
+ cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
4917
+ deleteBatch: (target, cutoff, limit) => db.pruneBatch({
4918
+ tableName: target.table,
4919
+ column: target.column,
4920
+ cutoff,
4921
+ limit
4922
+ })
4923
+ });
5016
4924
  }
5017
- /**
5018
- * Resolve a domain's `{ tableKey: policy }` map plus its descriptor into an
5019
- * ordered list of {@link PruneTarget}s. `order` lists table keys children-first
5020
- * so cascade-dependent rows are removed before their parents. Table keys not in
5021
- * `policies` are skipped (unset = keep forever).
5022
- */
5023
4925
  function resolveTargets({ policies, descriptor, order }) {
5024
- const targets = [];
5025
- for (const key of order) {
5026
- const policy = policies[key];
5027
- const entry = descriptor[key];
5028
- if (!policy || !entry) continue;
5029
- targets.push({
5030
- table: entry.table,
5031
- column: entry.column,
5032
- anchorType: entry.anchorType ?? "timestamp",
5033
- policy
5034
- });
5035
- }
5036
- return targets;
4926
+ return resolveRetentionTargets({
4927
+ policies,
4928
+ descriptor,
4929
+ order
4930
+ });
5037
4931
  }
5038
4932
  //#endregion
5039
4933
  //#region src/storage/domains/background-tasks/index.ts
@@ -10864,7 +10758,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
10864
10758
  hasMore: false
10865
10759
  };
10866
10760
  const limitValue = perPageInput === false ? total : perPage;
10867
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
10761
+ 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}`;
10868
10762
  return {
10869
10763
  threads: (await this.#db.readClient.manyOrNone(dataQuery, [
10870
10764
  ...queryParams,
@@ -11057,10 +10951,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11057
10951
  return messages.sort((a, b) => {
11058
10952
  const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
11059
10953
  const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
11060
- if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
10954
+ const idOrder = direction === "ASC" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id);
10955
+ if (aValue == null && bValue == null) return idOrder;
11061
10956
  if (aValue == null) return 1;
11062
10957
  if (bValue == null) return -1;
11063
- if (aValue === bValue) return a.id.localeCompare(b.id);
10958
+ if (aValue === bValue) return idOrder;
11064
10959
  if (typeof aValue === "number" && typeof bValue === "number") return direction === "ASC" ? aValue - bValue : bValue - aValue;
11065
10960
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
11066
10961
  });
@@ -11263,7 +11158,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11263
11158
  const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
11264
11159
  try {
11265
11160
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
11266
- const orderByStatement = `ORDER BY "${field}" ${direction}`;
11161
+ const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
11267
11162
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
11268
11163
  const tableName = getTableName$3({
11269
11164
  indexName: TABLE_MESSAGES,
@@ -11418,7 +11313,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11418
11313
  const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
11419
11314
  try {
11420
11315
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
11421
- const orderByStatement = `ORDER BY "${field}" ${direction}`;
11316
+ const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
11422
11317
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
11423
11318
  const tableName = getTableName$3({
11424
11319
  indexName: TABLE_MESSAGES,
@@ -14913,6 +14808,11 @@ function tableIndexes() {
14913
14808
  columns: "(\"tags\")",
14914
14809
  using: "gin"
14915
14810
  },
14811
+ {
14812
+ name: "mastra_score_events_scoreid_cursor_idx",
14813
+ table: TABLE_SCORE_EVENTS,
14814
+ columns: "(\"scoreId\", \"cursorId\" DESC)"
14815
+ },
14916
14816
  {
14917
14817
  name: "mastra_score_events_cursor_idx",
14918
14818
  table: TABLE_SCORE_EVENTS,
@@ -17045,13 +16945,32 @@ function pushScoreIdentity(acc, scorerId, scoreSource) {
17045
16945
  acc.params.push(scoreSource);
17046
16946
  }
17047
16947
  }
16948
+ function scoreRewriteConflict(row) {
16949
+ return `ON CONFLICT ("scoreId", "timestamp") DO UPDATE SET ${[
16950
+ ...Object.keys(row).filter((column) => column !== "scoreId" && column !== "timestamp").map((column) => `"${column}" = EXCLUDED."${column}"`),
16951
+ "\"cursorId\" = EXCLUDED.\"cursorId\"",
16952
+ "\"xactId\" = EXCLUDED.\"xactId\""
16953
+ ].join(", ")}`;
16954
+ }
16955
+ function collapseExactScoreConflicts(rows) {
16956
+ const records = /* @__PURE__ */ new Map();
16957
+ for (const row of rows) {
16958
+ const timestamp = new Date(row.timestamp).toISOString();
16959
+ const key = `${String(row.scoreId)}\u0000${timestamp}`;
16960
+ records.delete(key);
16961
+ records.set(key, row);
16962
+ }
16963
+ return [...records.values()];
16964
+ }
17048
16965
  async function createScore(client, schema, args) {
17049
- const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [scoreRecordToRow(args.score)]);
16966
+ const row = scoreRecordToRow(args.score);
16967
+ const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [row], scoreRewriteConflict(row));
17050
16968
  if (insert) await client.query(insert.text, insert.values);
17051
16969
  }
17052
16970
  async function batchCreateScores(client, schema, args) {
17053
16971
  if (args.scores.length === 0) return;
17054
- const insert = buildInsert(schema, TABLE_SCORE_EVENTS, args.scores.map(scoreRecordToRow));
16972
+ const rows = collapseExactScoreConflicts(args.scores.map(scoreRecordToRow));
16973
+ const insert = buildInsert(schema, TABLE_SCORE_EVENTS, rows, scoreRewriteConflict(rows[0]));
17055
16974
  if (insert) await client.query(insert.text, insert.values);
17056
16975
  }
17057
16976
  /**
@@ -17074,6 +16993,16 @@ async function deleteScores(client, schema, args) {
17074
16993
  }
17075
16994
  await client.query(`DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`, values);
17076
16995
  }
16996
+ function latestScorePredicate(table, alias = "s") {
16997
+ return `NOT EXISTS (
16998
+ SELECT 1 FROM ${table} newer
16999
+ WHERE newer."scoreId" = ${alias}."scoreId"
17000
+ AND newer."cursorId" > ${alias}."cursorId"
17001
+ )`;
17002
+ }
17003
+ function applyLatestScorePredicate(acc, table) {
17004
+ acc.conditions.push(latestScorePredicate(table));
17005
+ }
17077
17006
  async function listScores(client, schema, args) {
17078
17007
  const { mode, filters, pagination, orderBy, after, limit } = listScoresArgsSchema.parse(args);
17079
17008
  const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
@@ -17084,28 +17013,50 @@ async function listScores(client, schema, args) {
17084
17013
  return listScoresPage(client, table, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
17085
17014
  }
17086
17015
  async function getScoreById(client, schema, scoreId) {
17016
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17087
17017
  const row = await client.oneOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
17088
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17018
+ FROM ${table}
17089
17019
  WHERE "scoreId" = $1
17090
- ORDER BY "timestamp" DESC
17020
+ ORDER BY "cursorId" DESC
17091
17021
  LIMIT 1`, [scoreId]);
17092
17022
  return row ? rowToScoreRecord(row) : null;
17093
17023
  }
17094
17024
  async function listScoresPage(client, table, filters, page, perPage, orderField, orderDir) {
17095
- return listSignalPage({
17025
+ const acc = newFilterAccumulator();
17026
+ applyScoreFilters(acc, filters);
17027
+ applyLatestScorePredicate(acc, table);
17028
+ const whereClause = whereOrEmpty(acc);
17029
+ const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${table} s ${whereClause}`, acc.params);
17030
+ const total = Number(countRow?.count ?? 0);
17031
+ let scores = [];
17032
+ if (total > 0) {
17033
+ const safeOrderField = parseSqlIdentifier(orderField, "order field");
17034
+ scores = (await client.manyOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
17035
+ FROM ${table} s
17036
+ ${whereClause}
17037
+ ORDER BY "${safeOrderField}" ${orderDir}, "cursorId" ${orderDir}
17038
+ LIMIT $${acc.next++} OFFSET $${acc.next++}`, [
17039
+ ...acc.params,
17040
+ perPage,
17041
+ page * perPage
17042
+ ])).map(rowToScoreRecord);
17043
+ }
17044
+ const deltaCursor = deltaPollingFeatureEnabled() ? await readSignalStreamHeadCursor({
17096
17045
  client,
17097
17046
  table,
17098
17047
  filters,
17099
- page,
17100
- perPage,
17101
- orderField,
17102
- orderDir,
17103
- includeDeltaCursor: deltaPollingFeatureEnabled(),
17104
- selectColumns: SCORE_SELECT_COLUMNS,
17105
- responseKey: "scores",
17106
- applyFilters: applyScoreFilters,
17107
- mapRow: rowToScoreRecord
17108
- });
17048
+ applyFilters: applyScoreFilters
17049
+ }) : void 0;
17050
+ return {
17051
+ scores,
17052
+ pagination: {
17053
+ total,
17054
+ page,
17055
+ perPage,
17056
+ hasMore: (page + 1) * perPage < total
17057
+ },
17058
+ ...deltaCursor !== void 0 ? { deltaCursor } : {}
17059
+ };
17109
17060
  }
17110
17061
  async function listScoresDelta(client, table, filters, after, limit) {
17111
17062
  return listSignalDelta({
@@ -17121,12 +17072,14 @@ async function listScoresDelta(client, table, filters, after, limit) {
17121
17072
  });
17122
17073
  }
17123
17074
  async function runScoreAggregateQuery(client, schema, args, filters) {
17075
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17124
17076
  const acc = newFilterAccumulator();
17125
17077
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17126
17078
  applyScoreFilters(acc, filters);
17079
+ applyLatestScorePredicate(acc, table);
17127
17080
  const sql = `
17128
17081
  SELECT ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17129
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17082
+ FROM ${table} s
17130
17083
  ${whereOrEmpty(acc)}
17131
17084
  `;
17132
17085
  const row = await client.oneOrNone(sql, acc.params);
@@ -17158,10 +17111,12 @@ async function getScoreBreakdown(client, schema, args) {
17158
17111
  });
17159
17112
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17160
17113
  applyScoreFilters(acc, args.filters);
17114
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17115
+ applyLatestScorePredicate(acc, table);
17161
17116
  const sql = `
17162
17117
  SELECT ${resolved.map((e) => e.selectSql).join(", ")},
17163
17118
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17164
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17119
+ FROM ${table} s
17165
17120
  ${whereOrEmpty(acc)}
17166
17121
  GROUP BY ${resolved.map((e) => e.alias).join(", ")}
17167
17122
  ORDER BY "value" DESC NULLS LAST
@@ -17181,11 +17136,13 @@ async function getScoreTimeSeries(client, schema, args) {
17181
17136
  });
17182
17137
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17183
17138
  applyScoreFilters(acc, args.filters);
17139
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17140
+ applyLatestScorePredicate(acc, table);
17184
17141
  const sql = `
17185
17142
  SELECT ${bucket} AS bucket,
17186
17143
  ${resolved.map((e) => e.selectSql).join(", ")},
17187
17144
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17188
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17145
+ FROM ${table} s
17189
17146
  ${whereOrEmpty(acc)}
17190
17147
  GROUP BY bucket, ${resolved.map((e) => e.alias).join(", ")}
17191
17148
  ORDER BY bucket
@@ -17203,10 +17160,12 @@ async function getScoreTimeSeries(client, schema, args) {
17203
17160
  const acc = newFilterAccumulator();
17204
17161
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17205
17162
  applyScoreFilters(acc, args.filters);
17163
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17164
+ applyLatestScorePredicate(acc, table);
17206
17165
  const sql = `
17207
17166
  SELECT ${bucket} AS bucket,
17208
17167
  ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
17209
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17168
+ FROM ${table} s
17210
17169
  ${whereOrEmpty(acc)}
17211
17170
  GROUP BY bucket
17212
17171
  ORDER BY bucket
@@ -17226,9 +17185,11 @@ async function getScorePercentiles(client, schema, args) {
17226
17185
  const acc = newFilterAccumulator();
17227
17186
  pushScoreIdentity(acc, args.scorerId, args.scoreSource);
17228
17187
  applyScoreFilters(acc, args.filters);
17188
+ const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17189
+ applyLatestScorePredicate(acc, table);
17229
17190
  const sql = `
17230
17191
  SELECT ${bucket} AS bucket, ${percentileSelectSql(args.percentiles, "\"score\"")}
17231
- FROM ${qualifiedTable(schema, TABLE_SCORE_EVENTS)}
17192
+ FROM ${table} s
17232
17193
  ${whereOrEmpty(acc)}
17233
17194
  GROUP BY bucket
17234
17195
  ORDER BY bucket
@@ -17291,6 +17252,11 @@ const FEEDBACK_FIELDS = {
17291
17252
  const TRACE_SELECT = `
17292
17253
  r."traceId" AS "traceId",
17293
17254
  r."spanId" AS "rootSpanId",
17255
+ r."name" AS "name",
17256
+ r."entityId" AS "entityId",
17257
+ r."parentSpanId" AS "parentSpanId",
17258
+ r."metadata" AS "metadata",
17259
+ r."input" AS "input",
17294
17260
  r."threadId" AS "threadId",
17295
17261
  r."resourceId" AS "resourceId",
17296
17262
  r."startedAt" AS "startedAt",
@@ -17422,13 +17388,6 @@ function latestSpanPredicate$1(spanTable) {
17422
17388
  AND (newer."isPending" < s."isPending" OR (newer."isPending" = s."isPending" AND newer."cursorId" > s."cursorId"))
17423
17389
  )`;
17424
17390
  }
17425
- function latestScorePredicate(scoreTable) {
17426
- return `NOT EXISTS (
17427
- SELECT 1 FROM ${scoreTable} newer
17428
- WHERE newer."scoreId" = s."scoreId"
17429
- AND newer."cursorId" > s."cursorId"
17430
- )`;
17431
- }
17432
17391
  function latestFeedbackPredicate(feedbackTable) {
17433
17392
  return `NOT EXISTS (
17434
17393
  SELECT 1 FROM ${feedbackTable} newer
@@ -17598,7 +17557,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections) {
17598
17557
  values
17599
17558
  };
17600
17559
  }
17601
- function compilePostgresTraceQuery(schema, plan) {
17560
+ function compilePostgresTraceQuery(schema, plan, mode = "data") {
17602
17561
  const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
17603
17562
  let predicateSql = "TRUE";
17604
17563
  if (plan.where) {
@@ -17612,6 +17571,12 @@ function compilePostgresTraceQuery(schema, plan) {
17612
17571
  WHERE ${predicateSql}
17613
17572
  )`);
17614
17573
  const candidates = `WITH ${ctes.join(",\n")}`;
17574
+ if (plan.paginationMode === "page" && mode === "count") return {
17575
+ text: `${candidates}
17576
+ SELECT COUNT(*)::text AS count
17577
+ FROM candidates`,
17578
+ values
17579
+ };
17615
17580
  if (plan.result === "groups") {
17616
17581
  const pageCondition = plan.cursor ? `AND "threadId" > $${values.length + 1}` : "";
17617
17582
  if (plan.cursor) values.push(plan.cursor.threadId);
@@ -17629,6 +17594,17 @@ LIMIT $${values.length}`,
17629
17594
  }
17630
17595
  const orderField = plan.orderBy.field === "startedAt" ? "\"startedAt\"" : "\"endedAt\"";
17631
17596
  const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
17597
+ if (plan.paginationMode === "page") {
17598
+ values.push(plan.perPage, plan.page * plan.perPage);
17599
+ return {
17600
+ text: `${candidates}
17601
+ SELECT *
17602
+ FROM candidates
17603
+ ORDER BY ${orderField} ${direction}, "traceId" ASC
17604
+ LIMIT $${values.length - 1} OFFSET $${values.length}`,
17605
+ values
17606
+ };
17607
+ }
17632
17608
  let pageCondition = "";
17633
17609
  if (plan.cursor) {
17634
17610
  const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
@@ -17770,15 +17746,22 @@ function isPostgresStatementTimeout(error) {
17770
17746
  const candidate = error;
17771
17747
  return candidate.code === "57014" && String(candidate.message ?? "").includes("statement timeout");
17772
17748
  }
17773
- async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
17749
+ function isPostgresResourceLimit(error) {
17750
+ if (!error || typeof error !== "object") return false;
17751
+ const candidate = error;
17752
+ return candidate.code === "53200" || candidate.code === "53400";
17753
+ }
17754
+ async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute, options = {}) {
17774
17755
  const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
17775
17756
  try {
17776
17757
  return await client.tx(async (transaction) => {
17758
+ if (options.repeatableRead) await transaction.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
17777
17759
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${resolvedTimeoutMs}ms`]);
17778
17760
  return execute(transaction);
17779
17761
  });
17780
17762
  } catch (error) {
17781
17763
  if (isPostgresStatementTimeout(error)) throw new coreStorage.TraceQueryExecutionError();
17764
+ if (isPostgresResourceLimit(error)) throw new coreStorage.TraceQueryResourceLimitError();
17782
17765
  throw error;
17783
17766
  }
17784
17767
  }
@@ -17806,6 +17789,50 @@ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
17806
17789
  });
17807
17790
  }
17808
17791
  async function queryTraces(client, schema, plan, timeoutMs) {
17792
+ if (plan.paginationMode === "page") {
17793
+ const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
17794
+ const deadline = performance.now() + resolvedTimeoutMs;
17795
+ const countQuery = compilePostgresTraceQuery(schema, plan, "count");
17796
+ const dataQuery = compilePostgresTraceQuery(schema, plan);
17797
+ const { total, rows } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
17798
+ const countRows = await transaction.any(countQuery.text, countQuery.values);
17799
+ const remainingTimeoutMs = Math.floor(deadline - performance.now());
17800
+ if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
17801
+ await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
17802
+ const rows = await transaction.any(dataQuery.text, dataQuery.values);
17803
+ return {
17804
+ total: Number(countRows[0]?.count ?? 0),
17805
+ rows
17806
+ };
17807
+ }, { repeatableRead: true });
17808
+ const traces = rows.map((row) => ({
17809
+ traceId: String(row.traceId),
17810
+ rootSpanId: String(row.rootSpanId),
17811
+ name: row.name,
17812
+ entityId: row.entityId ?? null,
17813
+ parentSpanId: row.parentSpanId ?? null,
17814
+ createdAt: asIsoTimestamp$1(row.startedAt),
17815
+ metadata: row.metadata ?? null,
17816
+ inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
17817
+ threadId: row.threadId == null ? null : String(row.threadId),
17818
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
17819
+ startedAt: asIsoTimestamp$1(row.startedAt),
17820
+ endedAt: asIsoTimestamp$1(row.endedAt),
17821
+ entityName: row.entityName == null ? null : String(row.entityName),
17822
+ entityType: row.entityType == null ? null : String(row.entityType),
17823
+ environment: row.environment == null ? null : String(row.environment),
17824
+ status: row.status
17825
+ }));
17826
+ return coreStorage.traceQueryResponseSchema.parse({
17827
+ traces,
17828
+ pagination: {
17829
+ total,
17830
+ page: plan.page,
17831
+ perPage: plan.perPage,
17832
+ hasMore: (plan.page + 1) * plan.perPage < total
17833
+ }
17834
+ });
17835
+ }
17809
17836
  const query = compilePostgresTraceQuery(schema, plan);
17810
17837
  const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
17811
17838
  const visibleRows = rows.slice(0, plan.limit);
@@ -17823,6 +17850,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17823
17850
  const traces = visibleRows.map((row) => ({
17824
17851
  traceId: String(row.traceId),
17825
17852
  rootSpanId: String(row.rootSpanId),
17853
+ name: row.name,
17854
+ entityId: row.entityId ?? null,
17855
+ parentSpanId: row.parentSpanId ?? null,
17856
+ createdAt: asIsoTimestamp$1(row.startedAt),
17857
+ metadata: row.metadata ?? null,
17858
+ inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
17826
17859
  threadId: row.threadId == null ? null : String(row.threadId),
17827
17860
  resourceId: row.resourceId == null ? null : String(row.resourceId),
17828
17861
  startedAt: asIsoTimestamp$1(row.startedAt),
@@ -18570,7 +18603,7 @@ async function dangerouslyClearTracing(client, schema) {
18570
18603
  * Use it through `MastraCompositeStore` with a dedicated Postgres connection.
18571
18604
  */
18572
18605
  function wrapError(op, error, details) {
18573
- if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError) throw error;
18606
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
18574
18607
  throw new MastraError({
18575
18608
  id: createStorageErrorId("PG", op, "FAILED"),
18576
18609
  domain: ErrorDomain.STORAGE,