@mastra/pg 1.33.0-alpha.2 → 1.33.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.
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.33.0-alpha.2"
6
+ version: "1.33.0-alpha.3"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.33.0-alpha.2",
2
+ "version": "1.33.0-alpha.3",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -4019,6 +4019,42 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4019
4019
  }, error);
4020
4020
  }
4021
4021
  }
4022
+ /**
4023
+ * Runs one UPDATE per record in a transaction, changing a row only while every
4024
+ * column in `nullColumns` is still NULL. Never inserts. Returns, in input order,
4025
+ * whether each update changed a row.
4026
+ */
4027
+ async batchUpdateWhereNull({ tableName, updates, nullColumns }) {
4028
+ const nullConditions = nullColumns.map((column) => ` AND "${(0, _mastra_core_utils.parseSqlIdentifier)(column, "column name")}" IS NULL`).join("");
4029
+ try {
4030
+ return await this.client.tx(async (tx) => {
4031
+ const applied = [];
4032
+ for (const update of updates) {
4033
+ const query = await this.buildUpdateQuery({
4034
+ tableName,
4035
+ ...update
4036
+ });
4037
+ if (!query) {
4038
+ applied.push(false);
4039
+ continue;
4040
+ }
4041
+ const rows = await tx.any(`${query.sql}${nullConditions} RETURNING 1`, query.values);
4042
+ applied.push(rows.length > 0);
4043
+ }
4044
+ return applied;
4045
+ });
4046
+ } catch (error) {
4047
+ throw new _mastra_core_error.MastraError({
4048
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "BATCH_UPDATE_WHERE_NULL", "FAILED"),
4049
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
4050
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
4051
+ details: {
4052
+ tableName,
4053
+ numberOfRecords: updates.length
4054
+ }
4055
+ }, error);
4056
+ }
4057
+ }
4022
4058
  async batchDelete({ tableName, keys }) {
4023
4059
  try {
4024
4060
  if (keys.length === 0) return;
@@ -4139,9 +4175,14 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4139
4175
  columns: [column]
4140
4176
  });
4141
4177
  }
4142
- async executeUpdate(client, { tableName, keys, data }) {
4178
+ async executeUpdate(client, args) {
4179
+ const query = await this.buildUpdateQuery(args);
4180
+ if (!query) return;
4181
+ await client.none(query.sql, query.values);
4182
+ }
4183
+ async buildUpdateQuery({ tableName, keys, data }) {
4143
4184
  const filteredData = await this.filterRecordToKnownColumns(tableName, data);
4144
- if (Object.keys(filteredData).length === 0) return;
4185
+ if (Object.keys(filteredData).length === 0) return null;
4145
4186
  const setColumns = [];
4146
4187
  const setValues = [];
4147
4188
  let paramIndex = 1;
@@ -4157,12 +4198,13 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4157
4198
  whereConditions.push(`"${parsedKey}" = $${paramIndex++}`);
4158
4199
  whereValues.push(this.prepareValue(value, key, tableName));
4159
4200
  });
4160
- const sql = `UPDATE ${getTableName$6({
4161
- indexName: tableName,
4162
- schemaName: getSchemaName$6(this.schemaName)
4163
- })} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
4164
- const values = [...setValues, ...whereValues];
4165
- await client.none(sql, values);
4201
+ return {
4202
+ sql: `UPDATE ${getTableName$6({
4203
+ indexName: tableName,
4204
+ schemaName: getSchemaName$6(this.schemaName)
4205
+ })} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`,
4206
+ values: [...setValues, ...whereValues]
4207
+ };
4166
4208
  }
4167
4209
  };
4168
4210
  // src/storage/domains/utils.ts
@@ -12038,7 +12080,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
12038
12080
  indexName: OM_TABLE,
12039
12081
  schemaName: quotedSchemaName
12040
12082
  });
12041
- const idxPrefix = schemaPrefix ? `${schemaPrefix}` : "";
12083
+ const idxPrefix = schemaPrefix ? schemaPrefix : "";
12042
12084
  statements.push(`CREATE INDEX IF NOT EXISTS "${idxPrefix}idx_om_lookup_key" ON ${fullOmTableName} ("lookupKey");`);
12043
12085
  }
12044
12086
  for (const idx of MemoryPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
@@ -15712,30 +15754,33 @@ var ObservabilityPG = class ObservabilityPG extends _mastra_core_storage.Observa
15712
15754
  }, error);
15713
15755
  }
15714
15756
  }
15757
+ #toSpanUpdates(records) {
15758
+ return records.map((record) => {
15759
+ const data = { ...record.updates };
15760
+ if (data.endedAt instanceof Date) {
15761
+ const endedAt = data.endedAt.toISOString();
15762
+ data.endedAt = endedAt;
15763
+ data.endedAtZ = endedAt;
15764
+ }
15765
+ if (data.startedAt instanceof Date) {
15766
+ const startedAt = data.startedAt.toISOString();
15767
+ data.startedAt = startedAt;
15768
+ data.startedAtZ = startedAt;
15769
+ }
15770
+ return {
15771
+ keys: {
15772
+ spanId: record.spanId,
15773
+ traceId: record.traceId
15774
+ },
15775
+ data
15776
+ };
15777
+ });
15778
+ }
15715
15779
  async batchUpdateSpans(args) {
15716
15780
  try {
15717
15781
  return this.#db.batchUpdate({
15718
15782
  tableName: _mastra_core_storage.TABLE_SPANS,
15719
- updates: args.records.map((record) => {
15720
- const data = { ...record.updates };
15721
- if (data.endedAt instanceof Date) {
15722
- const endedAt = data.endedAt.toISOString();
15723
- data.endedAt = endedAt;
15724
- data.endedAtZ = endedAt;
15725
- }
15726
- if (data.startedAt instanceof Date) {
15727
- const startedAt = data.startedAt.toISOString();
15728
- data.startedAt = startedAt;
15729
- data.startedAtZ = startedAt;
15730
- }
15731
- return {
15732
- keys: {
15733
- spanId: record.spanId,
15734
- traceId: record.traceId
15735
- },
15736
- data
15737
- };
15738
- })
15783
+ updates: this.#toSpanUpdates(args.records)
15739
15784
  });
15740
15785
  } catch (error) {
15741
15786
  throw new _mastra_core_error.MastraError({
@@ -15745,6 +15790,25 @@ var ObservabilityPG = class ObservabilityPG extends _mastra_core_storage.Observa
15745
15790
  }, error);
15746
15791
  }
15747
15792
  }
15793
+ async batchUpdateUnendedSpans(args) {
15794
+ try {
15795
+ const applied = await this.#db.batchUpdateWhereNull({
15796
+ tableName: _mastra_core_storage.TABLE_SPANS,
15797
+ updates: this.#toSpanUpdates(args.records),
15798
+ nullColumns: ["endedAt"]
15799
+ });
15800
+ return { updated: args.records.filter((_, index) => applied[index]).map(({ traceId, spanId }) => ({
15801
+ traceId,
15802
+ spanId
15803
+ })) };
15804
+ } catch (error) {
15805
+ throw new _mastra_core_error.MastraError({
15806
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "BATCH_UPDATE_UNENDED_SPANS", "FAILED"),
15807
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
15808
+ category: _mastra_core_error.ErrorCategory.USER
15809
+ }, error);
15810
+ }
15811
+ }
15748
15812
  async batchDeleteTraces(args) {
15749
15813
  this.assertUnscopedBatchDeleteTraces(args);
15750
15814
  try {
@@ -20583,7 +20647,7 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20583
20647
  return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${values.length}`;
20584
20648
  }).join(" AND ")})`;
20585
20649
  }));
20586
- const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", "spanType", ${contextFields.map((field) => `"${field}"`).join(", ")},
20650
+ const payloads = await run(`SELECT "organizationId", "traceId", "cursorId", ${contextFields.map((field) => `"${field}"`).join(", ")},
20587
20651
  CASE WHEN "spanType" = 'agent_run' THEN "timeToFirstChunkMs" END AS "timeToFirstChunkMs",
20588
20652
  CASE WHEN error IS NULL THEN 'success' ELSE 'error' END AS status,
20589
20653
  ${[
@@ -20598,30 +20662,10 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20598
20662
  String(version)
20599
20663
  ]);
20600
20664
  const byRoot = new Map(payloads.map((row) => [key(row.organizationId, row.traceId, row.cursorId), row]));
20601
- const agents = page.filter((row) => byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId))?.spanType === "agent_run");
20602
- const models = /* @__PURE__ */ new Map();
20603
- if (agents.length) {
20604
- const modelValues = [];
20605
- const rows = await run(`SELECT DISTINCT ON ("organizationId", "traceId") "organizationId", "traceId",
20606
- CASE WHEN jsonb_typeof(attributes -> 'model') = 'string' THEN attributes ->> 'model' END AS model,
20607
- CASE WHEN jsonb_typeof(attributes -> 'provider') = 'string' THEN attributes ->> 'provider' END AS provider
20608
- FROM ${table} WHERE "spanType" = 'model_generation' AND NOT "isPending" AND (${agents.map((row) => `(${Object.entries({
20609
- organizationId: row.organizationId,
20610
- resourceId: row.resourceId,
20611
- traceId: row.firstTraceId,
20612
- parentSpanId: row.firstSpanId
20613
- }).map(([field, value]) => {
20614
- modelValues.push(value);
20615
- return `"${field}" ${field === "organizationId" || field === "resourceId" ? "IS NOT DISTINCT FROM" : "="} $${modelValues.length}`;
20616
- }).join(" AND ")})`).join(" OR ")})
20617
- ORDER BY "organizationId", "traceId", "startedAt", "spanId" COLLATE "C", "cursorId" DESC`, modelValues);
20618
- for (const row of rows) models.set(JSON.stringify([row.organizationId, row.traceId]), row);
20619
- }
20620
20665
  const threads = page.flatMap((row) => {
20621
20666
  const first = byRoot.get(key(row.organizationId, row.firstTraceId, row.firstCursorId));
20622
20667
  const last = byRoot.get(key(row.organizationId, row.lastTraceId, row.lastCursorId));
20623
20668
  if (!first || !last) return [];
20624
- const model = models.get(JSON.stringify([row.organizationId, row.firstTraceId]));
20625
20669
  return [_mastra_core_storage.threadQueryRowSchema.parse({
20626
20670
  organizationId: row.organizationId,
20627
20671
  threadId: row.threadId,
@@ -20633,8 +20677,6 @@ async function queryThreadRows(client, schema, plan, timeoutMs, strategy = "auto
20633
20677
  endedAt: row.endedAt.toISOString(),
20634
20678
  traceCount: Number(row.traceCount),
20635
20679
  timeToFirstChunkMs: first.timeToFirstChunkMs,
20636
- model: model?.model ?? null,
20637
- provider: model?.provider ?? null,
20638
20680
  inputPreview: readPreview(first.inputPreview, first.input),
20639
20681
  outputPreview: readPreview(last.outputPreview, last.output),
20640
20682
  errorPreview: readPreview(last.errorPreview, last.error)
@@ -25618,6 +25660,13 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
25618
25660
  supportsConcurrentUpdates() {
25619
25661
  return true;
25620
25662
  }
25663
+ supportsGuardedSnapshotWrites() {
25664
+ return true;
25665
+ }
25666
+ /** SQL for a snapshot's status. Legacy json/text columns can hold escapes that fail a jsonb cast (#11563). */
25667
+ async #statusExpression(column) {
25668
+ return await this.#db.getColumnType(_mastra_core_storage.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'`;
25669
+ }
25621
25670
  parseWorkflowRun(row) {
25622
25671
  let parsedSnapshot = row.snapshot;
25623
25672
  if (typeof parsedSnapshot === "string") try {
@@ -26013,20 +26062,22 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26013
26062
  }, error);
26014
26063
  }
26015
26064
  }
26016
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
26065
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence, expectedStatus }) {
26017
26066
  try {
26018
26067
  const now = /* @__PURE__ */ new Date();
26019
26068
  const createdAtValue = createdAt ? createdAt : now;
26020
26069
  const updatedAtValue = updatedAt ? updatedAt : now;
26021
26070
  const sanitizedSnapshot = toPgJson(snapshot);
26022
- await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "persistWorkflowSnapshot", (q) => q.none(`INSERT INTO ${getTableName({
26071
+ const guard = expectedStatus === void 0 ? "" : ` WHERE ${await this.#statusExpression("t.snapshot")} = ANY($9::text[])`;
26072
+ return await withRunFence(this.#db.client, this.#runOwnersTable(), (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "persistWorkflowSnapshot", (q) => q.oneOrNone(`INSERT INTO ${getTableName({
26023
26073
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26024
26074
  schemaName: getSchemaName(this.#schema)
26025
26075
  })} AS t
26026
26076
  (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
26027
26077
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
26028
26078
  ON CONFLICT (workflow_name, run_id) DO UPDATE
26029
- SET "resourceId" = COALESCE($3, t."resourceId"), snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8`, [
26079
+ SET "resourceId" = COALESCE($3, t."resourceId"), snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8${guard}
26080
+ RETURNING 1 AS written`, [
26030
26081
  workflowName,
26031
26082
  runId,
26032
26083
  resourceId,
@@ -26034,8 +26085,9 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26034
26085
  createdAtValue,
26035
26086
  updatedAtValue,
26036
26087
  createdAtValue,
26037
- updatedAtValue
26038
- ]));
26088
+ updatedAtValue,
26089
+ ...expectedStatus === void 0 ? [] : [Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus]]
26090
+ ])) !== null;
26039
26091
  } catch (error) {
26040
26092
  if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
26041
26093
  throw new _mastra_core_error.MastraError({
@@ -26045,6 +26097,25 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
26045
26097
  }, error);
26046
26098
  }
26047
26099
  }
26100
+ async loadWorkflowRunStatus({ workflowName, runId }) {
26101
+ try {
26102
+ const statusExpr = await this.#statusExpression("snapshot");
26103
+ return (await this.#db.client.oneOrNone(`SELECT ${statusExpr} AS status FROM ${getTableName({
26104
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
26105
+ schemaName: getSchemaName(this.#schema)
26106
+ })} WHERE "workflow_name" = $1 AND "run_id" = $2 ORDER BY "createdAt" DESC LIMIT 1`, [workflowName, runId]))?.status ?? null;
26107
+ } catch (error) {
26108
+ throw new _mastra_core_error.MastraError({
26109
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "LOAD_WORKFLOW_RUN_STATUS", "FAILED"),
26110
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
26111
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
26112
+ details: {
26113
+ workflowName,
26114
+ runId
26115
+ }
26116
+ }, error);
26117
+ }
26118
+ }
26048
26119
  async loadWorkflowSnapshot({ workflowName, runId }) {
26049
26120
  try {
26050
26121
  const result = await this.#db.load({