@mastra/pg 1.20.1-alpha.0 → 1.20.1-alpha.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 1.20.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Make `listWorkflowRuns` status filtering indexable on Postgres. The status predicate previously wrapped every snapshot in a `regexp_replace(snapshot::text, ...)::jsonb` sanitization step, which forced a sequential scan over the whole `mastra_workflow_snapshot` table. On `jsonb` snapshot columns Postgres already rejects the problematic Unicode escape sequences at insert time, so the sanitization was a no-op there and the query now uses a plain `snapshot->>'status'` comparison backed by a new default expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)`. Legacy tables whose snapshot column is still `json` or `text` keep the sanitizing path. ([#21684](https://github.com/mastra-ai/mastra/pull/21684))
8
+
9
+ - Updated dependencies [[`4e7a421`](https://github.com/mastra-ai/mastra/commit/4e7a421dce8a48742f785d1e93ad2f43a572b282), [`242e324`](https://github.com/mastra-ai/mastra/commit/242e3241e73cbd5c9bb86a31ebb49ca0256488d4), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`d774e89`](https://github.com/mastra-ai/mastra/commit/d774e8930c781df8c9effe3763e6b501c099b6cc), [`9c27a53`](https://github.com/mastra-ai/mastra/commit/9c27a53cd9d3de4f3f025bc387d94ce371c33f95), [`dff25a1`](https://github.com/mastra-ai/mastra/commit/dff25a1103fa72ee082a9b6f805ebeb5ce400753), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`7f78585`](https://github.com/mastra-ai/mastra/commit/7f785857e401570e2ffb316911f126ed363aa537), [`f2a4afd`](https://github.com/mastra-ai/mastra/commit/f2a4afd7e37e809669001ed17724b341a5c1f45e), [`d438148`](https://github.com/mastra-ai/mastra/commit/d438148e222c1e2fb3c652725ce75680962ebec4), [`ba05fe0`](https://github.com/mastra-ai/mastra/commit/ba05fe0738f70cb686777546e968237d09269142), [`d26a8d4`](https://github.com/mastra-ai/mastra/commit/d26a8d4281f28414715b333c85bedaf70d0b2890), [`677cdc6`](https://github.com/mastra-ai/mastra/commit/677cdc6af564dec29a13464d12b7ab2a4efc22e9), [`a318490`](https://github.com/mastra-ai/mastra/commit/a318490e17da32f338d50929c770d901a9b3dd72), [`763e0c6`](https://github.com/mastra-ai/mastra/commit/763e0c61e04d76ad9a9efd301aa57525ca0cbea9), [`23e0be2`](https://github.com/mastra-ai/mastra/commit/23e0be261381e49534b4ff3101c60ee64a946cbf), [`7fc8806`](https://github.com/mastra-ai/mastra/commit/7fc880627d3cbf995d31ea0e8b807bf15417e651), [`0e02eac`](https://github.com/mastra-ai/mastra/commit/0e02eacdb2e30e1697a41910b41163742a181dc1), [`4df174c`](https://github.com/mastra-ai/mastra/commit/4df174c32bddf093a82f273070b8380aef7c9e90), [`f7c25b5`](https://github.com/mastra-ai/mastra/commit/f7c25b5106ddfb48e591f98df7a51e0f2dd01dba), [`dc09cc1`](https://github.com/mastra-ai/mastra/commit/dc09cc1083d861cde192c1cd235324dc75b8c731), [`36b4649`](https://github.com/mastra-ai/mastra/commit/36b4649045a3a380cbab8ceca866db4086223aff), [`377eb81`](https://github.com/mastra-ai/mastra/commit/377eb81ce43b964e3a6b541df172da74a8ff3716)]:
10
+ - @mastra/core@1.60.0-alpha.8
11
+
3
12
  ## 1.20.1-alpha.0
4
13
 
5
14
  ### Patch Changes
@@ -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.20.1-alpha.0"
6
+ version: "1.20.1-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -19,7 +19,7 @@ Read the individual reference documents for detailed explanations and code examp
19
19
  - [Workers](references/docs-deployment-workers.md) - Separate background processing from the API layer by running workflow execution, cron schedules, and background tasks in dedicated worker processes.
20
20
  - [Semantic recall](references/docs-memory-semantic-recall.md) - Learn how to use semantic recall in Mastra to retrieve relevant messages from past conversations using vector search and embeddings.
21
21
  - [Working memory](references/docs-memory-working-memory.md) - Learn how to configure working memory in Mastra to store persistent user data, preferences.
22
- - [Storage](references/docs-storage-overview.md) - Configure storage for Mastra to persist runtime state across agents, workflows, observability, evals, schedules, and memory.
22
+ - [Storage](references/docs-storage.md) - Configure storage for Mastra to persist runtime state across agents, workflows, observability, evals, schedules, and memory.
23
23
 
24
24
  ### Integrations
25
25
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.20.1-alpha.0",
2
+ "version": "1.20.1-alpha.1",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -339,7 +339,7 @@ MASTRA_STEP_EXECUTION_URL=http://api:4111/api
339
339
 
340
340
  Without this variable, the orchestration worker attempts to execute steps in its own process, which doesn't have access to the full Mastra runtime in a split deployment.
341
341
 
342
- The endpoint uses the server's normal auth pipeline. If the API has an auth provider, set `MASTRA_WORKER_AUTH_TOKEN` to a bearer token that provider accepts. Mastra forwards the value as an `Authorization: Bearer` credential. The configured auth provider validates the token. See [Worker authentication](https://mastra.ai/docs/server/auth/workers) for server configuration and other credential formats.
342
+ The endpoint uses the server's normal auth pipeline. If the API has an auth provider, set `MASTRA_WORKER_AUTH_TOKEN` to a bearer token that provider accepts. Mastra forwards the value as an `Authorization: Bearer` credential. The configured auth provider validates the token. See [Worker authentication](https://mastra.ai/docs/auth/workers) for server configuration and other credential formats.
343
343
 
344
344
  ### Scale workers
345
345
 
@@ -365,18 +365,18 @@ A distributed PubSub backend persists unacknowledged events, which lets orchestr
365
365
 
366
366
  The scheduler calculates the next fire time from the current time after it restarts. It doesn't replay schedules that elapsed while it was unavailable.
367
367
 
368
- If the API crashes while a step is executing, that work can be lost and the workflow run can remain in a `running` state. See [known limitations](#known-limitations) and [durable agent crash recovery](https://mastra.ai/docs/long-running-agents/durable-agents).
368
+ If the API crashes while a step is executing, that work can be lost and the workflow run can remain in a `running` state. See [known limitations](#known-limitations) and [durable agent crash recovery](https://mastra.ai/docs/harness/durable-agents).
369
369
 
370
370
  ## Known limitations
371
371
 
372
372
  - **No dead-letter queue**: Failed events are nacked and retried, but there's no DLQ for events that fail after all retries.
373
373
  - **No built-in health endpoint**: Workers don't expose an HTTP health check. Use container-level liveness probes or process monitoring.
374
374
  - **Scheduler is single-instance**: Running multiple scheduler processes causes duplicate schedule fires.
375
- - **Runs stuck in "running" after API crash**: If the API process crashes while executing a workflow step, the run remains in `running` status with no automatic retry. For [durable agents](https://mastra.ai/docs/long-running-agents/durable-agents), set `recovery.durableAgents` to `'auto'` in the Mastra config to automatically re-drive orphaned runs on server restart. See [Crash recovery](https://mastra.ai/docs/long-running-agents/durable-agents) for details.
375
+ - **Runs stuck in "running" after API crash**: If the API process crashes while executing a workflow step, the run remains in `running` status with no automatic retry. For [durable agents](https://mastra.ai/docs/harness/durable-agents), set `recovery.durableAgents` to `'auto'` in the Mastra config to automatically re-drive orphaned runs on server restart. See [Crash recovery](https://mastra.ai/docs/harness/durable-agents) for details.
376
376
 
377
377
  ## Related
378
378
 
379
- - [Worker authentication](https://mastra.ai/docs/server/auth/workers): Secure worker-to-API communication
379
+ - [Worker authentication](https://mastra.ai/docs/auth/workers): Secure worker-to-API communication
380
380
  - [Workers reference](https://mastra.ai/reference/workers/overview): Details about worker environment variables and types, with a list of supported storage backends
381
381
  - [CLI reference](https://mastra.ai/reference/cli/mastra): `mastra worker build` and `mastra worker start`
382
382
  - [PubSub](https://mastra.ai/docs/server/pubsub): Event delivery backends
@@ -400,7 +400,7 @@ const response = await agent.generate('What do you know about me?', {
400
400
 
401
401
  ## Opt in to state signals (experimental)
402
402
 
403
- By default, working memory reaches the model as part of the system message. You can opt into delivering it as a [state signal](https://mastra.ai/docs/long-running-agents/signals) instead by setting `useStateSignals: true`:
403
+ By default, working memory reaches the model as part of the system message. You can opt into delivering it as a [state signal](https://mastra.ai/docs/harness/signals) instead by setting `useStateSignals: true`:
404
404
 
405
405
  ```typescript
406
406
  const memory = new Memory({
@@ -10,7 +10,7 @@ Storage powers:
10
10
  - [Workflows](https://mastra.ai/docs/workflows/overview): Durable snapshots for suspended and resumed workflow runs.
11
11
  - [Observability](https://mastra.ai/docs/observability/overview): Traces, spans, metrics, logs, and feedback.
12
12
  - [Evals](https://mastra.ai/docs/evals/overview): Scores, datasets, experiments, and evaluation results.
13
- - [Long-running agents](https://mastra.ai/docs/long-running-agents/durable-agents): Background tasks, schedules, goals, and thread state.
13
+ - [Long-running agents](https://mastra.ai/docs/harness/durable-agents): Background tasks, schedules, goals, and thread state.
14
14
 
15
15
  ## When to configure storage
16
16
 
@@ -408,6 +408,7 @@ PostgreSQL storage creates composite indexes during initialization for common qu
408
408
  - `mastra_ai_spans_scope_startedat_idx`: (scope, startedAt DESC)
409
409
  - `mastra_scores_trace_id_span_id_created_at_idx`: (traceId, spanId, createdAt DESC)
410
410
  - `mastra_workflow_snapshot_name_createdat_idx`: (workflow\_name, createdAt DESC)
411
+ - `mastra_workflow_snapshot_name_status_createdat_idx`: (workflow\_name, snapshot->>'status', createdAt DESC), created only when the `snapshot` column is `jsonb`
411
412
 
412
413
  These indexes improve performance for filtered queries with sorting, including `dateRange` filters on message queries and Studio's workflow runs-list.
413
414
 
package/dist/index.cjs CHANGED
@@ -2176,7 +2176,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2176
2176
  const schema = schemaName || "public";
2177
2177
  const [tableRows, columnRows, indexRows] = await Promise.all([
2178
2178
  client.manyOrNone(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = $1`, [schema]),
2179
- client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name
2179
+ client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type
2180
2180
  FROM pg_catalog.pg_class c
2181
2181
  JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2182
2182
  JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
@@ -2191,6 +2191,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2191
2191
  WHERE n.nspname = $1`, [schema])
2192
2192
  ]);
2193
2193
  const columns = /* @__PURE__ */ new Map();
2194
+ const columnTypes = /* @__PURE__ */ new Map();
2194
2195
  for (const row of columnRows) {
2195
2196
  let set = columns.get(row.table_name);
2196
2197
  if (!set) {
@@ -2198,6 +2199,12 @@ async function loadSchemaSnapshot(client, schemaName) {
2198
2199
  columns.set(row.table_name, set);
2199
2200
  }
2200
2201
  set.add(row.column_name);
2202
+ let types = columnTypes.get(row.table_name);
2203
+ if (!types) {
2204
+ types = /* @__PURE__ */ new Map();
2205
+ columnTypes.set(row.table_name, types);
2206
+ }
2207
+ types.set(row.column_name, row.data_type);
2201
2208
  }
2202
2209
  const indexes = /* @__PURE__ */ new Set();
2203
2210
  const replicaIdentityIndexes = /* @__PURE__ */ new Set();
@@ -2211,6 +2218,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2211
2218
  schemaName: schema,
2212
2219
  tables: new Set(tableRows.map((r) => r.tablename)),
2213
2220
  columns,
2221
+ columnTypes,
2214
2222
  indexes,
2215
2223
  replicaIdentityIndexes,
2216
2224
  primaryKeyIndexes
@@ -2464,6 +2472,8 @@ var PgDB = class extends _mastra_core_base.MastraBase {
2464
2472
  skipDefaultIndexes;
2465
2473
  /** Cache of actual table columns: tableName -> Set<columnName> */
2466
2474
  tableColumnsCache = /* @__PURE__ */ new Map();
2475
+ /** Cache of column Postgres data types: tableName -> columnName -> data_type */
2476
+ columnTypeCache = /* @__PURE__ */ new Map();
2467
2477
  constructor(config) {
2468
2478
  super({
2469
2479
  component: "STORAGE",
@@ -2538,7 +2548,9 @@ var PgDB = class extends _mastra_core_base.MastraBase {
2538
2548
  }
2539
2549
  }
2540
2550
  this.tableColumnsCache.delete(oldName);
2551
+ this.columnTypeCache.delete(oldName);
2541
2552
  this.tableColumnsCache.delete(newName);
2553
+ this.columnTypeCache.delete(newName);
2542
2554
  }
2543
2555
  /**
2544
2556
  * Records an out-of-band `DROP TABLE` in the init snapshot. See
@@ -2554,8 +2566,10 @@ var PgDB = class extends _mastra_core_base.MastraBase {
2554
2566
  if (snapshot) {
2555
2567
  snapshot.tables.delete(tableName);
2556
2568
  snapshot.columns.delete(tableName);
2569
+ snapshot.columnTypes.delete(tableName);
2557
2570
  }
2558
2571
  this.tableColumnsCache.delete(tableName);
2572
+ this.columnTypeCache.delete(tableName);
2559
2573
  }
2560
2574
  /**
2561
2575
  * Records an out-of-band `ALTER TABLE … ADD COLUMN` in the init snapshot.
@@ -2565,6 +2579,7 @@ var PgDB = class extends _mastra_core_base.MastraBase {
2565
2579
  const snapshot = this.schemaSnapshot;
2566
2580
  if (snapshot) this.snapshotColumns(snapshot, tableName).add(column);
2567
2581
  this.tableColumnsCache.delete(tableName);
2582
+ this.columnTypeCache.delete(tableName);
2568
2583
  }
2569
2584
  /**
2570
2585
  * Gets the set of column names that actually exist in the database table.
@@ -2607,6 +2622,50 @@ var PgDB = class extends _mastra_core_base.MastraBase {
2607
2622
  ]);
2608
2623
  }
2609
2624
  /**
2625
+ * Returns the Postgres data type of a column (e.g. `jsonb`, `json`, `text`),
2626
+ * or null when the table or column does not exist.
2627
+ *
2628
+ * Answered from the init snapshot when one is installed, so a warm `init()`
2629
+ * issues no catalog probe. Outside init, results are cached per instance and
2630
+ * the cache is invalidated alongside {@link tableColumnsCache} whenever DDL
2631
+ * changes a table.
2632
+ */
2633
+ async getColumnType(table, column) {
2634
+ const snapshot = this.schemaSnapshot;
2635
+ if (snapshot) {
2636
+ const types = snapshot.columnTypes.get(table);
2637
+ const known = types?.get(column) ?? types?.get(column.toLowerCase());
2638
+ if (known) return known;
2639
+ if (types) return null;
2640
+ }
2641
+ const cached = this.columnTypeCache.get(table)?.get(column);
2642
+ if (cached !== void 0) return cached;
2643
+ const schema = this.schemaName || "public";
2644
+ const dataType = (await this.client.oneOrNone(`SELECT data_type FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND (column_name = $3 OR column_name = $4)`, [
2645
+ schema,
2646
+ table,
2647
+ column,
2648
+ column.toLowerCase()
2649
+ ]))?.data_type ?? null;
2650
+ if (dataType) {
2651
+ if (snapshot) {
2652
+ let snapshotTypes = snapshot.columnTypes.get(table);
2653
+ if (!snapshotTypes) {
2654
+ snapshotTypes = /* @__PURE__ */ new Map();
2655
+ snapshot.columnTypes.set(table, snapshotTypes);
2656
+ }
2657
+ snapshotTypes.set(column, dataType);
2658
+ }
2659
+ let types = this.columnTypeCache.get(table);
2660
+ if (!types) {
2661
+ types = /* @__PURE__ */ new Map();
2662
+ this.columnTypeCache.set(table, types);
2663
+ }
2664
+ types.set(column, dataType);
2665
+ }
2666
+ return dataType;
2667
+ }
2668
+ /**
2610
2669
  * Prepares values for insertion, handling JSONB columns by stringifying them
2611
2670
  */
2612
2671
  prepareValuesForInsert(record, tableName) {
@@ -2805,6 +2864,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
2805
2864
  }, error);
2806
2865
  } finally {
2807
2866
  this.tableColumnsCache.delete(tableName);
2867
+ this.columnTypeCache.delete(tableName);
2808
2868
  }
2809
2869
  }
2810
2870
  async setupTimestampTriggers(tableName) {
@@ -3127,6 +3187,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3127
3187
  }, error);
3128
3188
  } finally {
3129
3189
  this.tableColumnsCache.delete(tableName);
3190
+ this.columnTypeCache.delete(tableName);
3130
3191
  }
3131
3192
  }
3132
3193
  async load({ tableName, keys }) {
@@ -3190,6 +3251,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3190
3251
  }, error);
3191
3252
  } finally {
3192
3253
  this.tableColumnsCache.delete(tableName);
3254
+ this.columnTypeCache.delete(tableName);
3193
3255
  }
3194
3256
  }
3195
3257
  async createIndex(options) {
@@ -19322,6 +19384,29 @@ function getTableName({ indexName, schemaName }) {
19322
19384
  const quotedIndexName = `"${indexName}"`;
19323
19385
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
19324
19386
  }
19387
+ /** Base name (before any schema prefix) of the expression index backing the status filter. */
19388
+ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_createdat_idx";
19389
+ /**
19390
+ * Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
19391
+ * stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
19392
+ * no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
19393
+ */
19394
+ function workflowSnapshotStatusIndexName(schemaName) {
19395
+ return buildConstraintName({
19396
+ baseName: WORKFLOW_SNAPSHOT_STATUS_INDEX,
19397
+ schemaName: schemaName && schemaName !== "public" ? schemaName : void 0
19398
+ });
19399
+ }
19400
+ /**
19401
+ * Expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)` so
19402
+ * listWorkflowRuns() status filters can use an index instead of scanning every snapshot.
19403
+ */
19404
+ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
19405
+ return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
19406
+ indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
19407
+ schemaName: getSchemaName(schemaName)
19408
+ })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
19409
+ }
19325
19410
  /**
19326
19411
  * Sanitizes JSON string for PostgreSQL jsonb:
19327
19412
  * - Removes problematic Unicode sequences:
@@ -19404,6 +19489,7 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
19404
19489
  includeAllConstraints: true
19405
19490
  }));
19406
19491
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
19492
+ statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
19407
19493
  return statements;
19408
19494
  }
19409
19495
  /**
@@ -19423,6 +19509,13 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
19423
19509
  } catch (error) {
19424
19510
  this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
19425
19511
  }
19512
+ if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
19513
+ const indexName = workflowSnapshotStatusIndexName(this.#schema);
19514
+ try {
19515
+ await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema));
19516
+ } catch (error) {
19517
+ this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
19518
+ }
19426
19519
  }
19427
19520
  async init() {
19428
19521
  await this.#db.createTable({
@@ -19712,7 +19805,8 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
19712
19805
  paramIndex++;
19713
19806
  }
19714
19807
  if (status) {
19715
- conditions.push(`regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status' = $${paramIndex}`);
19808
+ const statusExpr = await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb" ? `snapshot ->> 'status'` : `regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status'`;
19809
+ conditions.push(`${statusExpr} = $${paramIndex}`);
19716
19810
  values.push(status);
19717
19811
  paramIndex++;
19718
19812
  }