@mastra/pg 1.26.0-alpha.3 → 1.26.0-alpha.5

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, 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";
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, resolveListOrderBy, 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";
@@ -2336,6 +2336,8 @@ var RoutingDbClient = class {
2336
2336
  return this.active.tx(callback);
2337
2337
  }
2338
2338
  };
2339
+ /** Bytes reserved for the `_xxxxxxxx` collision suffix when hashWhenTruncated applies. */
2340
+ const TRUNCATION_HASH_SUFFIX_LENGTH = 9;
2339
2341
  function truncateIdentifier(value, maxLength = 63) {
2340
2342
  if (maxLength <= 0) return "";
2341
2343
  if (Buffer.byteLength(value, "utf-8") <= maxLength) return value;
@@ -2356,9 +2358,25 @@ function truncateIdentifier(value, maxLength = 63) {
2356
2358
  * in system catalogs (pg_constraint.conname, pg_indexes.indexname, etc.).
2357
2359
  * Without this normalisation, runtime lookups that compare a mixed-case name
2358
2360
  * against the catalog would silently fail.
2361
+ *
2362
+ * With `hashWhenTruncated`, a name that exceeds the limit is truncated further
2363
+ * to make room for `_` + 8 hex chars of the full name's sha256. Plain
2364
+ * truncation cuts the tail, so two names sharing a long `<schema>_<prefix>`
2365
+ * collapse to the same identifier and `CREATE INDEX IF NOT EXISTS` (which
2366
+ * matches by name only) silently skips the second one. The suffix is
2367
+ * deterministic, so creation, warm-init snapshot checks, and DDL export all
2368
+ * agree on the same name. Opt-in because renaming already-released constraint
2369
+ * names would orphan the existing objects in deployed catalogs.
2359
2370
  */
2360
- function buildConstraintName({ baseName, schemaName, maxLength = 63 }) {
2361
- return truncateIdentifier(`${schemaName ? `${schemaName}_` : ""}${baseName}`.toLowerCase(), maxLength);
2371
+ function buildConstraintName({ baseName, schemaName, maxLength = 63, hashWhenTruncated = false }) {
2372
+ const fullName = `${schemaName ? `${schemaName}_` : ""}${baseName}`.toLowerCase();
2373
+ if (hashWhenTruncated && Buffer.byteLength(fullName, "utf-8") > maxLength) {
2374
+ const suffixLength = Math.min(TRUNCATION_HASH_SUFFIX_LENGTH, maxLength);
2375
+ if (suffixLength < 2) return truncateIdentifier(fullName, maxLength);
2376
+ const hash = createHash("sha256").update(fullName).digest("hex").slice(0, suffixLength - 1);
2377
+ return `${truncateIdentifier(fullName, maxLength - suffixLength)}_${hash}`;
2378
+ }
2379
+ return truncateIdentifier(fullName, maxLength);
2362
2380
  }
2363
2381
  //#endregion
2364
2382
  //#region src/storage/db/pg-errors.ts
@@ -6094,6 +6112,14 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6094
6112
  }
6095
6113
  async listDatasets(args) {
6096
6114
  try {
6115
+ const orderBy = resolveListOrderBy(args.orderBy, [
6116
+ "createdAt",
6117
+ "updatedAt",
6118
+ "name"
6119
+ ], {
6120
+ field: "createdAt",
6121
+ direction: "DESC"
6122
+ });
6097
6123
  const { page, perPage: perPageInput } = args.pagination;
6098
6124
  const tableName = getTableName$5({
6099
6125
  indexName: TABLE_DATASETS,
@@ -6149,7 +6175,7 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6149
6175
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
6150
6176
  const limitValue = perPageInput === false ? total : perPage;
6151
6177
  return {
6152
- datasets: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "createdAt" DESC, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
6178
+ datasets: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
6153
6179
  ...queryParams,
6154
6180
  limitValue,
6155
6181
  offset
@@ -6716,6 +6742,10 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6716
6742
  */
6717
6743
  async #listItems(client, args) {
6718
6744
  try {
6745
+ const orderBy = resolveListOrderBy(args.orderBy, ["createdAt", "updatedAt"], {
6746
+ field: "createdAt",
6747
+ direction: "DESC"
6748
+ });
6719
6749
  const { page, perPage: perPageInput } = args.pagination;
6720
6750
  const tableName = getTableName$5({
6721
6751
  indexName: TABLE_DATASET_ITEMS,
@@ -6766,7 +6796,7 @@ var DatasetsPG = class DatasetsPG extends DatasetsStorage {
6766
6796
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
6767
6797
  const limitValue = perPageInput === false ? total : perPage;
6768
6798
  return {
6769
- items: (await client.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "createdAt" DESC, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
6799
+ items: (await client.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
6770
6800
  ...queryParams,
6771
6801
  limitValue,
6772
6802
  offset
@@ -7326,6 +7356,10 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7326
7356
  }
7327
7357
  async listExperiments(args) {
7328
7358
  try {
7359
+ const orderBy = resolveListOrderBy(args.orderBy, ["createdAt", "status"], {
7360
+ field: "createdAt",
7361
+ direction: "DESC"
7362
+ });
7329
7363
  const { page, perPage: perPageInput } = args.pagination;
7330
7364
  const tableName = getTableName$5({
7331
7365
  indexName: TABLE_EXPERIMENTS,
@@ -7397,7 +7431,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7397
7431
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
7398
7432
  const limitValue = perPageInput === false ? total : perPage;
7399
7433
  return {
7400
- experiments: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7434
+ experiments: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7401
7435
  ...queryParams,
7402
7436
  limitValue,
7403
7437
  offset
@@ -7669,6 +7703,10 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7669
7703
  }
7670
7704
  async listExperimentResults(args) {
7671
7705
  try {
7706
+ const orderBy = resolveListOrderBy(args.orderBy, ["startedAt", "createdAt"], {
7707
+ field: "startedAt",
7708
+ direction: "ASC"
7709
+ });
7672
7710
  const { page, perPage: perPageInput } = args.pagination;
7673
7711
  const tableName = getTableName$5({
7674
7712
  indexName: TABLE_EXPERIMENT_RESULTS,
@@ -7716,7 +7754,7 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
7716
7754
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
7717
7755
  const limitValue = perPageInput === false ? total : perPage;
7718
7756
  return {
7719
- results: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "startedAt" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7757
+ results: (await this.#db.readClient.manyOrNone(`SELECT * FROM ${tableName} ${whereClause} ORDER BY "${orderBy.field}" ${orderBy.direction}, "id" ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, [
7720
7758
  ...queryParams,
7721
7759
  limitValue,
7722
7760
  offset
@@ -17255,7 +17293,7 @@ const TRACE_SELECT = `
17255
17293
  r."name" AS "name",
17256
17294
  r."entityId" AS "entityId",
17257
17295
  r."parentSpanId" AS "parentSpanId",
17258
- r."metadata" AS "metadata",
17296
+ r."metadataRaw" AS "metadata",
17259
17297
  r."input" AS "input",
17260
17298
  r."threadId" AS "threadId",
17261
17299
  r."resourceId" AS "resourceId",
@@ -17473,22 +17511,27 @@ function compileThreadPredicate(predicate, parameterOffset) {
17473
17511
  values: compiled.values
17474
17512
  };
17475
17513
  }
17476
- function compilePostgresTraceScope(schema, selection, relationCollections) {
17514
+ function compilePostgresTraceScope(schema, selection, relationCollections, deltaWindow) {
17477
17515
  const spanTable = qualifiedTable(schema, TABLE_SPAN_EVENTS);
17478
17516
  const scoreTable = qualifiedTable(schema, TABLE_SCORE_EVENTS);
17479
17517
  const feedbackTable = qualifiedTable(schema, TABLE_FEEDBACK_EVENTS);
17480
17518
  const values = [selection.timeRange.from, selection.timeRange.to];
17481
- const ctes = [`root_scope AS MATERIALIZED (
17482
- SELECT *
17483
- FROM ${spanTable} r
17484
- WHERE ${[
17519
+ const rootConditions = [
17485
17520
  `r."parentSpanId" IS NULL`,
17486
17521
  latestRootPredicate$1(spanTable),
17487
17522
  `NOT r."isPending"`,
17488
17523
  `r."endedAt" IS NOT NULL`,
17489
17524
  `r."startedAt" >= $1`,
17490
17525
  `r."startedAt" < $2`
17491
- ].join("\n AND ")}
17526
+ ];
17527
+ if (deltaWindow) {
17528
+ values.push(deltaWindow.xactId, deltaWindow.cursorId, deltaWindow.safeHorizon);
17529
+ rootConditions.push(`(r."xactId", r."cursorId") > ($3::xid8, $4::bigint)`, `r."xactId" < $5::xid8`);
17530
+ }
17531
+ const ctes = [`root_scope AS MATERIALIZED (
17532
+ SELECT *
17533
+ FROM ${spanTable} r
17534
+ WHERE ${rootConditions.join("\n AND ")}
17492
17535
  )`];
17493
17536
  if (relationCollections.has("spans")) ctes.push(`current_spans AS MATERIALIZED (
17494
17537
  SELECT
@@ -17557,8 +17600,18 @@ function compilePostgresTraceScope(schema, selection, relationCollections) {
17557
17600
  values
17558
17601
  };
17559
17602
  }
17560
- function compilePostgresTraceQuery(schema, plan, mode = "data") {
17561
- const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
17603
+ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
17604
+ const relationCollections = collectRelationCollections(plan.where);
17605
+ let deltaWindow;
17606
+ if (plan.paginationMode === "delta") {
17607
+ const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "pg");
17608
+ if (watermark === void 0 || safeHorizon === void 0) throw new Error("Delta query requires a cursor and safe horizon");
17609
+ deltaWindow = {
17610
+ ...decodeTraceDeltaWatermark(watermark),
17611
+ safeHorizon
17612
+ };
17613
+ }
17614
+ const { ctes, values } = compilePostgresTraceScope(schema, plan, relationCollections, deltaWindow);
17562
17615
  let predicateSql = "TRUE";
17563
17616
  if (plan.where) {
17564
17617
  const predicate = compilePredicate(plan.where, values.length + 1);
@@ -17566,7 +17619,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data") {
17566
17619
  values.push(...predicate.values);
17567
17620
  }
17568
17621
  ctes.push(`candidates AS (
17569
- SELECT ${TRACE_SELECT}
17622
+ SELECT ${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}
17570
17623
  FROM root_scope r
17571
17624
  WHERE ${predicateSql}
17572
17625
  )`);
@@ -17577,6 +17630,16 @@ SELECT COUNT(*)::text AS count
17577
17630
  FROM candidates`,
17578
17631
  values
17579
17632
  };
17633
+ if (plan.paginationMode === "delta") {
17634
+ values.push(plan.limit + 1);
17635
+ return {
17636
+ text: `${candidates}
17637
+ SELECT * FROM candidates
17638
+ ORDER BY "xactId" ASC, "cursorId" ASC
17639
+ LIMIT $${values.length}`,
17640
+ values
17641
+ };
17642
+ }
17580
17643
  if (plan.result === "groups") {
17581
17644
  const pageCondition = plan.cursor ? `AND "threadId" > $${values.length + 1}` : "";
17582
17645
  if (plan.cursor) values.push(plan.cursor.threadId);
@@ -17788,13 +17851,83 @@ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
17788
17851
  valuesTruncated: rows.length > plan.limit
17789
17852
  });
17790
17853
  }
17854
+ function decodeTraceDeltaWatermark(watermark) {
17855
+ try {
17856
+ return decodeDeltaCursor(watermark);
17857
+ } catch {
17858
+ throw new coreStorage.TraceQueryCursorError("TRACE_QUERY_CURSOR_MALFORMED");
17859
+ }
17860
+ }
17861
+ function emptyDeltaWatermark(horizon, watermark) {
17862
+ if (watermark !== void 0 && BigInt(decodeTraceDeltaWatermark(watermark).xactId) >= BigInt(horizon)) return watermark;
17863
+ return encodeDeltaCursor(horizon, 0);
17864
+ }
17865
+ async function setRemainingTimeout(transaction, deadline) {
17866
+ const remainingTimeoutMs = Math.floor(deadline - performance.now());
17867
+ if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
17868
+ await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
17869
+ }
17870
+ function traceRowToResult(row) {
17871
+ return {
17872
+ traceId: String(row.traceId),
17873
+ rootSpanId: String(row.rootSpanId),
17874
+ name: row.name,
17875
+ entityId: row.entityId ?? null,
17876
+ parentSpanId: row.parentSpanId ?? null,
17877
+ createdAt: asIsoTimestamp$1(row.startedAt),
17878
+ metadata: row.metadata ?? null,
17879
+ inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
17880
+ threadId: row.threadId == null ? null : String(row.threadId),
17881
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
17882
+ startedAt: asIsoTimestamp$1(row.startedAt),
17883
+ endedAt: asIsoTimestamp$1(row.endedAt),
17884
+ entityName: row.entityName == null ? null : String(row.entityName),
17885
+ entityType: row.entityType == null ? null : String(row.entityType),
17886
+ environment: row.environment == null ? null : String(row.environment),
17887
+ status: row.status
17888
+ };
17889
+ }
17791
17890
  async function queryTraces(client, schema, plan, timeoutMs) {
17891
+ if (plan.paginationMode === "delta") {
17892
+ assertDeltaPollingEnabled();
17893
+ const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "pg");
17894
+ if (watermark !== void 0) decodeTraceDeltaWatermark(watermark);
17895
+ const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
17896
+ const deadline = performance.now() + resolvedTimeoutMs;
17897
+ return runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
17898
+ await setRemainingTimeout(transaction, deadline);
17899
+ const horizon = await readSafeXactHorizon(transaction);
17900
+ let rows = [];
17901
+ if (watermark !== void 0) {
17902
+ await setRemainingTimeout(transaction, deadline);
17903
+ const query = compilePostgresTraceQuery(schema, plan, "data", horizon);
17904
+ rows = await transaction.any(query.text, query.values);
17905
+ }
17906
+ const visible = rows.slice(0, plan.limit);
17907
+ const last = visible.at(-1);
17908
+ return coreStorage.traceQueryResponseSchema.parse({
17909
+ traces: visible.map(traceRowToResult),
17910
+ delta: {
17911
+ limit: plan.limit,
17912
+ hasMore: rows.length > plan.limit
17913
+ },
17914
+ deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "pg", last ? encodeDeltaCursor(last.xactId, last.cursorId) : emptyDeltaWatermark(horizon, watermark))
17915
+ });
17916
+ }, { repeatableRead: true });
17917
+ }
17792
17918
  if (plan.paginationMode === "page") {
17793
17919
  const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
17794
17920
  const deadline = performance.now() + resolvedTimeoutMs;
17795
17921
  const countQuery = compilePostgresTraceQuery(schema, plan, "count");
17796
17922
  const dataQuery = compilePostgresTraceQuery(schema, plan);
17797
- const { total, rows } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
17923
+ const { total, rows, deltaCursor } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
17924
+ let deltaCursor;
17925
+ if (deltaPollingFeatureEnabled() && typeof coreStorage.encodeTraceQueryDeltaCursor === "function") {
17926
+ await setRemainingTimeout(transaction, deadline);
17927
+ const horizon = await readSafeXactHorizon(transaction);
17928
+ deltaCursor = coreStorage.encodeTraceQueryDeltaCursor(plan, "pg", encodeDeltaCursor(horizon, 0));
17929
+ }
17930
+ if (deltaCursor !== void 0) await setRemainingTimeout(transaction, deadline);
17798
17931
  const countRows = await transaction.any(countQuery.text, countQuery.values);
17799
17932
  const remainingTimeoutMs = Math.floor(deadline - performance.now());
17800
17933
  if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
@@ -17802,29 +17935,14 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17802
17935
  const rows = await transaction.any(dataQuery.text, dataQuery.values);
17803
17936
  return {
17804
17937
  total: Number(countRows[0]?.count ?? 0),
17805
- rows
17938
+ rows,
17939
+ deltaCursor
17806
17940
  };
17807
17941
  }, { 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
- }));
17942
+ const traces = rows.map(traceRowToResult);
17826
17943
  return coreStorage.traceQueryResponseSchema.parse({
17827
17944
  traces,
17945
+ ...deltaCursor === void 0 ? {} : { deltaCursor },
17828
17946
  pagination: {
17829
17947
  total,
17830
17948
  page: plan.page,
@@ -17847,24 +17965,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
17847
17965
  }) : null }
17848
17966
  });
17849
17967
  }
17850
- const traces = visibleRows.map((row) => ({
17851
- traceId: String(row.traceId),
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,
17859
- threadId: row.threadId == null ? null : String(row.threadId),
17860
- resourceId: row.resourceId == null ? null : String(row.resourceId),
17861
- startedAt: asIsoTimestamp$1(row.startedAt),
17862
- endedAt: asIsoTimestamp$1(row.endedAt),
17863
- entityName: row.entityName == null ? null : String(row.entityName),
17864
- entityType: row.entityType == null ? null : String(row.entityType),
17865
- environment: row.environment == null ? null : String(row.environment),
17866
- status: row.status
17867
- }));
17968
+ const traces = visibleRows.map(traceRowToResult);
17868
17969
  const last = traces.at(-1);
17869
17970
  return coreStorage.traceQueryResponseSchema.parse({
17870
17971
  traces,
@@ -18603,7 +18704,7 @@ async function dangerouslyClearTracing(client, schema) {
18603
18704
  * Use it through `MastraCompositeStore` with a dedicated Postgres connection.
18604
18705
  */
18605
18706
  function wrapError(op, error, details) {
18606
- if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
18707
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryCursorError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
18607
18708
  throw new MastraError({
18608
18709
  id: createStorageErrorId("PG", op, "FAILED"),
18609
18710
  domain: ErrorDomain.STORAGE,
@@ -22293,6 +22394,7 @@ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_cre
22293
22394
  * Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
22294
22395
  * stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
22295
22396
  * no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
22397
+ * Exported for tests.
22296
22398
  */
22297
22399
  function workflowSnapshotStatusIndexName(schemaName) {
22298
22400
  return buildConstraintName({
@@ -22310,6 +22412,48 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
22310
22412
  schemaName: getSchemaName(schemaName)
22311
22413
  })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
22312
22414
  }
22415
+ /** Base name (before any schema prefix) of the expression index backing the threadId filter. */
22416
+ const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
22417
+ /**
22418
+ * Schema-prefixed name of the threadId index (see workflowSnapshotStatusIndexName).
22419
+ *
22420
+ * Unlike the status index, truncation appends a collision hash: both index names share the
22421
+ * long `<schema>_mastra_workflow_snapshot_` prefix, so with a schema name of 37+ bytes plain
22422
+ * truncation collapses them to the same 63-byte identifier and `CREATE INDEX IF NOT EXISTS`
22423
+ * silently skips this index. The status index keeps plain truncation because its truncated
22424
+ * name already exists in deployed catalogs; this index is new and free to adopt the rule.
22425
+ * Exported for tests.
22426
+ */
22427
+ function workflowSnapshotThreadIdIndexName(schemaName) {
22428
+ return buildConstraintName({
22429
+ baseName: WORKFLOW_SNAPSHOT_THREAD_ID_INDEX,
22430
+ schemaName: schemaName && schemaName !== "public" ? schemaName : void 0,
22431
+ hashWhenTruncated: true
22432
+ });
22433
+ }
22434
+ /**
22435
+ * Expression extracting the thread id embedded in a snapshot (jsonb columns only). Mirrors
22436
+ * the canonical extraction in `@mastra/core` (`getSnapshotMemoryInfo`), which reads one of
22437
+ * two layouts:
22438
+ * 1. agentic-loop: `context.<suspended step>.suspendPayload.__streamState.messageList.memoryInfo.threadId`
22439
+ * 2. durable loop: `context.input.messageListState.memoryInfo.threadId`
22440
+ *
22441
+ * `jsonb_path_query_first(jsonb, jsonpath)` is IMMUTABLE, so the expression is valid in an
22442
+ * expression index. The WHERE clause in listWorkflowRuns() must use this exact expression
22443
+ * text so the planner can match it against the index. If the snapshot layout changes in
22444
+ * core, this expression must be updated in lockstep or it will wrongly exclude rows.
22445
+ */
22446
+ const WORKFLOW_SNAPSHOT_THREAD_ID_EXPR = `COALESCE(jsonb_path_query_first(snapshot, '$.context.* ? (@.status == "suspended").suspendPayload.__streamState.messageList.memoryInfo.threadId') #>> '{}', snapshot #>> '{context,input,messageListState,memoryInfo,threadId}')`;
22447
+ /**
22448
+ * Expression index on the snapshot-embedded thread id so listWorkflowRuns() threadId filters
22449
+ * (Agent.listSuspendedRuns) can use an index instead of detoasting every snapshot.
22450
+ */
22451
+ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName) {
22452
+ return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
22453
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
22454
+ schemaName: getSchemaName(schemaName)
22455
+ })} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
22456
+ }
22313
22457
  var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22314
22458
  #db;
22315
22459
  #schema;
@@ -22382,6 +22526,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22382
22526
  }));
22383
22527
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
22384
22528
  statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
22529
+ statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
22385
22530
  return statements;
22386
22531
  }
22387
22532
  /**
@@ -22408,6 +22553,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22408
22553
  } catch (error) {
22409
22554
  this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
22410
22555
  }
22556
+ const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
22557
+ try {
22558
+ await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema));
22559
+ } catch (error) {
22560
+ this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
22561
+ }
22411
22562
  }
22412
22563
  async init() {
22413
22564
  await this.#db.createTable({
@@ -22688,7 +22839,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22688
22839
  }, error);
22689
22840
  }
22690
22841
  }
22691
- async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, status } = {}) {
22842
+ async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status } = {}) {
22692
22843
  try {
22693
22844
  const conditions = [];
22694
22845
  const values = [];
@@ -22709,6 +22860,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22709
22860
  values.push(resourceId);
22710
22861
  paramIndex++;
22711
22862
  } else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
22863
+ if (threadId) if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
22864
+ conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
22865
+ values.push(threadId);
22866
+ paramIndex++;
22867
+ } else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
22712
22868
  if (fromDate) {
22713
22869
  conditions.push(`"createdAt" >= $${paramIndex}`);
22714
22870
  values.push(fromDate);