@mastra/pg 1.33.0-alpha.2 → 1.33.0

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,6 +1,6 @@
1
1
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
2
2
  import * as coreStorage from "@mastra/core/storage";
3
- 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_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, 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_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_MEMORY_RUN_FENCES, 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_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertWorkflowStepGuard, calculatePagination, canonicalizeKnowledgeScope, computeTraceStatus, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, isWorkflowStepConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
3
+ 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_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, 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_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_MEMORY_RUN_FENCES, 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_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertWorkflowStepGuard, calculatePagination, canonicalizeKnowledgeScope, computeTraceStatus, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, getStringAttribute, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, isWorkflowStepConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
4
  import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
5
5
  import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
6
6
  import { Mutex } from "async-mutex";
@@ -239,7 +239,7 @@ const createNumericOperator = (symbol) => {
239
239
  sql: `(CASE WHEN jsonb_typeof(${jsonExtract}) = 'number' THEN (${textExtract})::numeric ${symbol} $${paramIndex}::numeric ELSE NULL END)`,
240
240
  needsValue: true
241
241
  };
242
- else return {
242
+ return {
243
243
  sql: `${textExtract} ${symbol} $${paramIndex}::text`,
244
244
  needsValue: true
245
245
  };
@@ -3996,6 +3996,42 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3996
3996
  }, error);
3997
3997
  }
3998
3998
  }
3999
+ /**
4000
+ * Runs one UPDATE per record in a transaction, changing a row only while every
4001
+ * column in `nullColumns` is still NULL. Never inserts. Returns, in input order,
4002
+ * whether each update changed a row.
4003
+ */
4004
+ async batchUpdateWhereNull({ tableName, updates, nullColumns }) {
4005
+ const nullConditions = nullColumns.map((column) => ` AND "${parseSqlIdentifier(column, "column name")}" IS NULL`).join("");
4006
+ try {
4007
+ return await this.client.tx(async (tx) => {
4008
+ const applied = [];
4009
+ for (const update of updates) {
4010
+ const query = await this.buildUpdateQuery({
4011
+ tableName,
4012
+ ...update
4013
+ });
4014
+ if (!query) {
4015
+ applied.push(false);
4016
+ continue;
4017
+ }
4018
+ const rows = await tx.any(`${query.sql}${nullConditions} RETURNING 1`, query.values);
4019
+ applied.push(rows.length > 0);
4020
+ }
4021
+ return applied;
4022
+ });
4023
+ } catch (error) {
4024
+ throw new MastraError({
4025
+ id: createStorageErrorId("PG", "BATCH_UPDATE_WHERE_NULL", "FAILED"),
4026
+ domain: ErrorDomain.STORAGE,
4027
+ category: ErrorCategory.THIRD_PARTY,
4028
+ details: {
4029
+ tableName,
4030
+ numberOfRecords: updates.length
4031
+ }
4032
+ }, error);
4033
+ }
4034
+ }
3999
4035
  async batchDelete({ tableName, keys }) {
4000
4036
  try {
4001
4037
  if (keys.length === 0) return;
@@ -4116,9 +4152,14 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4116
4152
  columns: [column]
4117
4153
  });
4118
4154
  }
4119
- async executeUpdate(client, { tableName, keys, data }) {
4155
+ async executeUpdate(client, args) {
4156
+ const query = await this.buildUpdateQuery(args);
4157
+ if (!query) return;
4158
+ await client.none(query.sql, query.values);
4159
+ }
4160
+ async buildUpdateQuery({ tableName, keys, data }) {
4120
4161
  const filteredData = await this.filterRecordToKnownColumns(tableName, data);
4121
- if (Object.keys(filteredData).length === 0) return;
4162
+ if (Object.keys(filteredData).length === 0) return null;
4122
4163
  const setColumns = [];
4123
4164
  const setValues = [];
4124
4165
  let paramIndex = 1;
@@ -4134,12 +4175,13 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4134
4175
  whereConditions.push(`"${parsedKey}" = $${paramIndex++}`);
4135
4176
  whereValues.push(this.prepareValue(value, key, tableName));
4136
4177
  });
4137
- const sql = `UPDATE ${getTableName$6({
4138
- indexName: tableName,
4139
- schemaName: getSchemaName$6(this.schemaName)
4140
- })} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
4141
- const values = [...setValues, ...whereValues];
4142
- await client.none(sql, values);
4178
+ return {
4179
+ sql: `UPDATE ${getTableName$6({
4180
+ indexName: tableName,
4181
+ schemaName: getSchemaName$6(this.schemaName)
4182
+ })} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`,
4183
+ values: [...setValues, ...whereValues]
4184
+ };
4143
4185
  }
4144
4186
  };
4145
4187
  // src/storage/domains/utils.ts
@@ -12015,7 +12057,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12015
12057
  indexName: OM_TABLE,
12016
12058
  schemaName: quotedSchemaName
12017
12059
  });
12018
- const idxPrefix = schemaPrefix ? `${schemaPrefix}` : "";
12060
+ const idxPrefix = schemaPrefix ? schemaPrefix : "";
12019
12061
  statements.push(`CREATE INDEX IF NOT EXISTS "${idxPrefix}idx_om_lookup_key" ON ${fullOmTableName} ("lookupKey");`);
12020
12062
  }
12021
12063
  for (const idx of MemoryPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
@@ -15689,30 +15731,33 @@ var ObservabilityPG = class ObservabilityPG extends ObservabilityStorage {
15689
15731
  }, error);
15690
15732
  }
15691
15733
  }
15734
+ #toSpanUpdates(records) {
15735
+ return records.map((record) => {
15736
+ const data = { ...record.updates };
15737
+ if (data.endedAt instanceof Date) {
15738
+ const endedAt = data.endedAt.toISOString();
15739
+ data.endedAt = endedAt;
15740
+ data.endedAtZ = endedAt;
15741
+ }
15742
+ if (data.startedAt instanceof Date) {
15743
+ const startedAt = data.startedAt.toISOString();
15744
+ data.startedAt = startedAt;
15745
+ data.startedAtZ = startedAt;
15746
+ }
15747
+ return {
15748
+ keys: {
15749
+ spanId: record.spanId,
15750
+ traceId: record.traceId
15751
+ },
15752
+ data
15753
+ };
15754
+ });
15755
+ }
15692
15756
  async batchUpdateSpans(args) {
15693
15757
  try {
15694
15758
  return this.#db.batchUpdate({
15695
15759
  tableName: TABLE_SPANS,
15696
- updates: args.records.map((record) => {
15697
- const data = { ...record.updates };
15698
- if (data.endedAt instanceof Date) {
15699
- const endedAt = data.endedAt.toISOString();
15700
- data.endedAt = endedAt;
15701
- data.endedAtZ = endedAt;
15702
- }
15703
- if (data.startedAt instanceof Date) {
15704
- const startedAt = data.startedAt.toISOString();
15705
- data.startedAt = startedAt;
15706
- data.startedAtZ = startedAt;
15707
- }
15708
- return {
15709
- keys: {
15710
- spanId: record.spanId,
15711
- traceId: record.traceId
15712
- },
15713
- data
15714
- };
15715
- })
15760
+ updates: this.#toSpanUpdates(args.records)
15716
15761
  });
15717
15762
  } catch (error) {
15718
15763
  throw new MastraError({
@@ -15722,6 +15767,25 @@ var ObservabilityPG = class ObservabilityPG extends ObservabilityStorage {
15722
15767
  }, error);
15723
15768
  }
15724
15769
  }
15770
+ async batchUpdateUnendedSpans(args) {
15771
+ try {
15772
+ const applied = await this.#db.batchUpdateWhereNull({
15773
+ tableName: TABLE_SPANS,
15774
+ updates: this.#toSpanUpdates(args.records),
15775
+ nullColumns: ["endedAt"]
15776
+ });
15777
+ return { updated: args.records.filter((_, index) => applied[index]).map(({ traceId, spanId }) => ({
15778
+ traceId,
15779
+ spanId
15780
+ })) };
15781
+ } catch (error) {
15782
+ throw new MastraError({
15783
+ id: createStorageErrorId("PG", "BATCH_UPDATE_UNENDED_SPANS", "FAILED"),
15784
+ domain: ErrorDomain.STORAGE,
15785
+ category: ErrorCategory.USER
15786
+ }, error);
15787
+ }
15788
+ }
15725
15789
  async batchDeleteTraces(args) {
15726
15790
  this.assertUnscopedBatchDeleteTraces(args);
15727
15791
  try {
@@ -16039,6 +16103,51 @@ const SPAN_EVENT_COLUMNS = [
16039
16103
  type: "double precision",
16040
16104
  nullable: true
16041
16105
  },
16106
+ {
16107
+ name: "externalParentSpanId",
16108
+ type: "text",
16109
+ nullable: true
16110
+ },
16111
+ {
16112
+ name: "resumedFromSpanId",
16113
+ type: "text",
16114
+ nullable: true
16115
+ },
16116
+ {
16117
+ name: "model",
16118
+ type: "text",
16119
+ nullable: true
16120
+ },
16121
+ {
16122
+ name: "provider",
16123
+ type: "text",
16124
+ nullable: true
16125
+ },
16126
+ {
16127
+ name: "outcome",
16128
+ type: "text",
16129
+ nullable: true
16130
+ },
16131
+ {
16132
+ name: "stopReason",
16133
+ type: "text",
16134
+ nullable: true
16135
+ },
16136
+ {
16137
+ name: "timeToFirstTokenMs",
16138
+ type: "double precision",
16139
+ nullable: true
16140
+ },
16141
+ {
16142
+ name: "errorCount",
16143
+ type: "integer",
16144
+ nullable: true
16145
+ },
16146
+ {
16147
+ name: "stopReasonContext",
16148
+ type: "jsonb",
16149
+ nullable: true
16150
+ },
16042
16151
  {
16043
16152
  name: "tags",
16044
16153
  type: "text[]",
@@ -16747,6 +16856,26 @@ function allTableDDL(schema, mode) {
16747
16856
  ];
16748
16857
  }
16749
16858
  /**
16859
+ * Span event columns added after the table first shipped, with their types. All
16860
+ * nullable with no default, so each ALTER is a catalog-only change; rows written
16861
+ * before it read NULL and readers fall back to the payload or attributes.
16862
+ */
16863
+ const ADDED_SPAN_COLUMNS = [
16864
+ ["inputPreview", "text"],
16865
+ ["outputPreview", "text"],
16866
+ ["errorPreview", "text"],
16867
+ ["timeToFirstChunkMs", "double precision"],
16868
+ ["externalParentSpanId", "text"],
16869
+ ["resumedFromSpanId", "text"],
16870
+ ["model", "text"],
16871
+ ["provider", "text"],
16872
+ ["outcome", "text"],
16873
+ ["stopReason", "text"],
16874
+ ["timeToFirstTokenMs", "double precision"],
16875
+ ["errorCount", "integer"],
16876
+ ["stopReasonContext", "jsonb"]
16877
+ ];
16878
+ /**
16750
16879
  * Additive column migrations for tables created by an older version of this
16751
16880
  * schema. `CREATE TABLE IF NOT EXISTS` is a no-op on an existing table, so new
16752
16881
  * columns have to be added out of band.
@@ -16763,20 +16892,11 @@ function additiveColumns(schema) {
16763
16892
  column: "isPending",
16764
16893
  ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "isPending" boolean NOT NULL DEFAULT false`
16765
16894
  },
16766
- ...[
16767
- "inputPreview",
16768
- "outputPreview",
16769
- "errorPreview"
16770
- ].map((column) => ({
16895
+ ...ADDED_SPAN_COLUMNS.map(([column, type]) => ({
16771
16896
  table: TABLE_SPAN_EVENTS,
16772
16897
  column,
16773
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "${column}" text`
16898
+ ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "${column}" ${type}`
16774
16899
  })),
16775
- {
16776
- table: TABLE_SPAN_EVENTS,
16777
- column: "timeToFirstChunkMs",
16778
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "timeToFirstChunkMs" double precision`
16779
- },
16780
16900
  {
16781
16901
  table: TABLE_FEEDBACK_EVENTS,
16782
16902
  column: "reviewStatus",
@@ -17329,6 +17449,11 @@ const PROMOTED_KEYS = /* @__PURE__ */ new Set([
17329
17449
  "executionSource",
17330
17450
  "serviceName"
17331
17451
  ]);
17452
+ function nullableNumber$1(value) {
17453
+ if (value == null || value === "") return null;
17454
+ const n = Number(value);
17455
+ return Number.isNaN(n) ? null : n;
17456
+ }
17332
17457
  function nullableString(value) {
17333
17458
  if (typeof value === "string") return value === "" ? null : value;
17334
17459
  if (value == null) return null;
@@ -17490,6 +17615,15 @@ function spanRecordToRow(span) {
17490
17615
  startedAt: toIsoOrDate(span.startedAt),
17491
17616
  endedAt: toIsoOrDate(endedAt),
17492
17617
  timeToFirstChunkMs: span.timeToFirstChunkMs ?? null,
17618
+ externalParentSpanId: span.externalParentSpanId ?? null,
17619
+ resumedFromSpanId: span.resumedFromSpanId ?? null,
17620
+ model: span.model ?? null,
17621
+ provider: span.provider ?? null,
17622
+ outcome: span.outcome ?? null,
17623
+ stopReason: span.stopReason ?? null,
17624
+ stopReasonContext: jsonField(span.stopReasonContext),
17625
+ timeToFirstTokenMs: span.timeToFirstTokenMs ?? null,
17626
+ errorCount: span.errorCount ?? null,
17493
17627
  tags: normalizeTags(span.tags),
17494
17628
  metadataSearch: buildMetadataSearch(metadata),
17495
17629
  metadataRaw: jsonField(metadata),
@@ -17519,6 +17653,7 @@ function rowToSpanRecord(row) {
17519
17653
  const startedAt = toDate(row.startedAt);
17520
17654
  const endedAt = spanRowEndedAt(row, startedAt);
17521
17655
  const error = parsedJson(row.error);
17656
+ const attributes = parsedJson(row.attributes);
17522
17657
  const { executionSource, ...ctx } = rowToCommonContext(row);
17523
17658
  return {
17524
17659
  ...ctx,
@@ -17531,10 +17666,19 @@ function rowToSpanRecord(row) {
17531
17666
  isEvent: Boolean(row.isEvent),
17532
17667
  startedAt,
17533
17668
  endedAt,
17669
+ externalParentSpanId: nullableString(row.externalParentSpanId),
17670
+ resumedFromSpanId: nullableString(row.resumedFromSpanId),
17671
+ model: nullableString(row.model) ?? getStringAttribute(attributes, "model"),
17672
+ provider: nullableString(row.provider) ?? getStringAttribute(attributes, "provider"),
17673
+ outcome: nullableString(row.outcome),
17674
+ stopReason: nullableString(row.stopReason),
17675
+ stopReasonContext: parsedJson(row.stopReasonContext) ?? null,
17676
+ timeToFirstTokenMs: nullableNumber$1(row.timeToFirstTokenMs),
17677
+ errorCount: nullableNumber$1(row.errorCount),
17534
17678
  tags: normalizeTags(row.tags),
17535
17679
  metadata: parsedJson(row.metadataRaw) ?? void 0,
17536
17680
  scope: parsedJson(row.scope) ?? void 0,
17537
- attributes: parsedJson(row.attributes) ?? void 0,
17681
+ attributes: attributes ?? void 0,
17538
17682
  links: parsedJson(row.links) ?? void 0,
17539
17683
  input: parsedJson(row.input) ?? void 0,
17540
17684
  output: parsedJson(row.output) ?? void 0,
@@ -20560,7 +20704,7 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20560
20704
  return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${values.length}`;
20561
20705
  }).join(" AND ")})`;
20562
20706
  }));
20563
- const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", "spanType", ${contextFields.map((field) => `"${field}"`).join(", ")},
20707
+ const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", ${contextFields.map((field) => `"${field}"`).join(", ")},
20564
20708
  CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs",
20565
20709
  CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
20566
20710
  ${[
@@ -20575,30 +20719,10 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20575
20719
  String(version)
20576
20720
  ]);
20577
20721
  const byRoot = new Map(payloads.map((row) => [key(row.organizationId, row.traceId, row.cursorId), row]));
20578
- const agents = page.filter((row) => byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId))?.spanType === "agent_run");
20579
- const models = /* @__PURE__ */ new Map();
20580
- if (agents.length) {
20581
- const modelValues = [];
20582
- const rows = await run(`SELECT DISTINCT ON ("organizationId", "traceId") "organizationId", "traceId",
20583
- CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20584
- CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider
20585
- FROM ${table} WHERE "spanType" = 'model_generation' AND NOT "isPending" AND (${agents.map((row) => `(${Object.entries({
20586
- organizationId: row.organizationId,
20587
- resourceId: row.resourceId,
20588
- traceId: row.firstTraceId,
20589
- parentSpanId: row.firstSpanId
20590
- }).map(([field, value]) => {
20591
- modelValues.push(value);
20592
- return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${modelValues.length}`;
20593
- }).join(" AND ")})`).join(" OR ")})
20594
- ORDER BY "organizationId", "traceId", "startedAt", "spanId" COLLATE "C", "cursorId" DESC`, modelValues);
20595
- for (const row of rows) models.set(JSON.stringify([row.organizationId, row.traceId]), row);
20596
- }
20597
20722
  const threads = page.flatMap((row) => {
20598
20723
  const first = byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId));
20599
20724
  const last = byRoot.get(key(row.organizationId, row.lastTraceId, row.lastCursorId));
20600
20725
  if (!first || !last) return [];
20601
- const model = models.get(JSON.stringify([row.organizationId, row.firstTraceId]));
20602
20726
  return [coreStorage.threadQueryRowSchema.parse({
20603
20727
  organizationId: row.organizationId,
20604
20728
  threadId: row.threadId,
@@ -20610,8 +20734,6 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20610
20734
  endedAt: row.endedAt.toISOString(),
20611
20735
  traceCount: Number(row.traceCount),
20612
20736
  timeToFirstChunkMs: first.timeToFirstChunkMs,
20613
- model: model?.model ?? null,
20614
- provider: model?.provider ?? null,
20615
20737
  inputPreview: readPreview(first.inputPreview, first.input),
20616
20738
  outputPreview: readPreview(last.outputPreview, last.output),
20617
20739
  errorPreview: readPreview(last.errorPreview, last.error)
@@ -25595,6 +25717,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
25595
25717
  supportsConcurrentUpdates() {
25596
25718
  return true;
25597
25719
  }
25720
+ supportsGuardedSnapshotWrites() {
25721
+ return true;
25722
+ }
25723
+ /** SQL for a snapshot's status. Legacy json/text columns can hold escapes that fail a jsonb cast (#11563). */
25724
+ async #statusExpression(column) {
25725
+ return await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb" ? `${column} ->> 'status'` : `regexp_replace(${column}::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status'`;
25726
+ }
25598
25727
  parseWorkflowRun(row) {
25599
25728
  let parsedSnapshot = row.snapshot;
25600
25729
  if (typeof parsedSnapshot === "string") try {
@@ -25990,20 +26119,22 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
25990
26119
  }, error);
25991
26120
  }
25992
26121
  }
25993
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
26122
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence, expectedStatus }) {
25994
26123
  try {
25995
26124
  const now = /* @__PURE__ */ new Date();
25996
26125
  const createdAtValue = createdAt ? createdAt : now;
25997
26126
  const updatedAtValue = updatedAt ? updatedAt : now;
25998
26127
  const sanitizedSnapshot = toPgJson(snapshot);
25999
- await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", (q) => q.none(`INSERT INTO ${getTableName({
26128
+ const guard = expectedStatus === void 0 ? "" : ` WHERE ${await this.#statusExpression("t.snapshot")} = ANY($9::text[])`;
26129
+ return await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", (q) => q.oneOrNone(`INSERT INTO ${getTableName({
26000
26130
  indexName: TABLE_WORKFLOW_SNAPSHOT,
26001
26131
  schemaName: getSchemaName(this.#schema)
26002
26132
  })} AS t
26003
26133
  (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
26004
26134
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
26005
26135
  ON CONFLICT (workflow_name, run_id) DO UPDATE
26006
- SET "resourceId" = COALESCE($3, t."resourceId"), snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8`, [
26136
+ SET "resourceId" = COALESCE($3, t."resourceId"), snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8${guard}
26137
+ RETURNING 1 AS written`, [
26007
26138
  workflowName,
26008
26139
  runId,
26009
26140
  resourceId,
@@ -26011,8 +26142,9 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
26011
26142
  createdAtValue,
26012
26143
  updatedAtValue,
26013
26144
  createdAtValue,
26014
- updatedAtValue
26015
- ]));
26145
+ updatedAtValue,
26146
+ ...expectedStatus === void 0 ? [] : [Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus]]
26147
+ ])) !== null;
26016
26148
  } catch (error) {
26017
26149
  if (isRunFenceConflictError(error)) throw error;
26018
26150
  throw new MastraError({
@@ -26022,6 +26154,25 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
26022
26154
  }, error);
26023
26155
  }
26024
26156
  }
26157
+ async loadWorkflowRunStatus({ workflowName, runId }) {
26158
+ try {
26159
+ const statusExpr = await this.#statusExpression("snapshot");
26160
+ return (await this.#db.client.oneOrNone(`SELECT ${statusExpr} AS status FROM ${getTableName({
26161
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
26162
+ schemaName: getSchemaName(this.#schema)
26163
+ })} WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1`, [workflowName, runId]))?.status ?? null;
26164
+ } catch (error) {
26165
+ throw new MastraError({
26166
+ id: createStorageErrorId("PG", "LOAD_WORKFLOW_RUN_STATUS", "FAILED"),
26167
+ domain: ErrorDomain.STORAGE,
26168
+ category: ErrorCategory.THIRD_PARTY,
26169
+ details: {
26170
+ workflowName,
26171
+ runId
26172
+ }
26173
+ }, error);
26174
+ }
26175
+ }
26025
26176
  async loadWorkflowSnapshot({ workflowName, runId }) {
26026
26177
  try {
26027
26178
  const result = await this.#db.load({