@mastra/pg 1.20.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/dist/index.js CHANGED
@@ -2152,7 +2152,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2152
2152
  const schema = schemaName || "public";
2153
2153
  const [tableRows, columnRows, indexRows] = await Promise.all([
2154
2154
  client.manyOrNone(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = $1`, [schema]),
2155
- client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name
2155
+ client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type
2156
2156
  FROM pg_catalog.pg_class c
2157
2157
  JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2158
2158
  JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
@@ -2167,6 +2167,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2167
2167
  WHERE n.nspname = $1`, [schema])
2168
2168
  ]);
2169
2169
  const columns = /* @__PURE__ */ new Map();
2170
+ const columnTypes = /* @__PURE__ */ new Map();
2170
2171
  for (const row of columnRows) {
2171
2172
  let set = columns.get(row.table_name);
2172
2173
  if (!set) {
@@ -2174,6 +2175,12 @@ async function loadSchemaSnapshot(client, schemaName) {
2174
2175
  columns.set(row.table_name, set);
2175
2176
  }
2176
2177
  set.add(row.column_name);
2178
+ let types = columnTypes.get(row.table_name);
2179
+ if (!types) {
2180
+ types = /* @__PURE__ */ new Map();
2181
+ columnTypes.set(row.table_name, types);
2182
+ }
2183
+ types.set(row.column_name, row.data_type);
2177
2184
  }
2178
2185
  const indexes = /* @__PURE__ */ new Set();
2179
2186
  const replicaIdentityIndexes = /* @__PURE__ */ new Set();
@@ -2187,6 +2194,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2187
2194
  schemaName: schema,
2188
2195
  tables: new Set(tableRows.map((r) => r.tablename)),
2189
2196
  columns,
2197
+ columnTypes,
2190
2198
  indexes,
2191
2199
  replicaIdentityIndexes,
2192
2200
  primaryKeyIndexes
@@ -2440,6 +2448,8 @@ var PgDB = class extends MastraBase {
2440
2448
  skipDefaultIndexes;
2441
2449
  /** Cache of actual table columns: tableName -> Set<columnName> */
2442
2450
  tableColumnsCache = /* @__PURE__ */ new Map();
2451
+ /** Cache of column Postgres data types: tableName -> columnName -> data_type */
2452
+ columnTypeCache = /* @__PURE__ */ new Map();
2443
2453
  constructor(config) {
2444
2454
  super({
2445
2455
  component: "STORAGE",
@@ -2514,7 +2524,9 @@ var PgDB = class extends MastraBase {
2514
2524
  }
2515
2525
  }
2516
2526
  this.tableColumnsCache.delete(oldName);
2527
+ this.columnTypeCache.delete(oldName);
2517
2528
  this.tableColumnsCache.delete(newName);
2529
+ this.columnTypeCache.delete(newName);
2518
2530
  }
2519
2531
  /**
2520
2532
  * Records an out-of-band `DROP TABLE` in the init snapshot. See
@@ -2530,8 +2542,10 @@ var PgDB = class extends MastraBase {
2530
2542
  if (snapshot) {
2531
2543
  snapshot.tables.delete(tableName);
2532
2544
  snapshot.columns.delete(tableName);
2545
+ snapshot.columnTypes.delete(tableName);
2533
2546
  }
2534
2547
  this.tableColumnsCache.delete(tableName);
2548
+ this.columnTypeCache.delete(tableName);
2535
2549
  }
2536
2550
  /**
2537
2551
  * Records an out-of-band `ALTER TABLE … ADD COLUMN` in the init snapshot.
@@ -2541,6 +2555,7 @@ var PgDB = class extends MastraBase {
2541
2555
  const snapshot = this.schemaSnapshot;
2542
2556
  if (snapshot) this.snapshotColumns(snapshot, tableName).add(column);
2543
2557
  this.tableColumnsCache.delete(tableName);
2558
+ this.columnTypeCache.delete(tableName);
2544
2559
  }
2545
2560
  /**
2546
2561
  * Gets the set of column names that actually exist in the database table.
@@ -2583,6 +2598,50 @@ var PgDB = class extends MastraBase {
2583
2598
  ]);
2584
2599
  }
2585
2600
  /**
2601
+ * Returns the Postgres data type of a column (e.g. `jsonb`, `json`, `text`),
2602
+ * or null when the table or column does not exist.
2603
+ *
2604
+ * Answered from the init snapshot when one is installed, so a warm `init()`
2605
+ * issues no catalog probe. Outside init, results are cached per instance and
2606
+ * the cache is invalidated alongside {@link tableColumnsCache} whenever DDL
2607
+ * changes a table.
2608
+ */
2609
+ async getColumnType(table, column) {
2610
+ const snapshot = this.schemaSnapshot;
2611
+ if (snapshot) {
2612
+ const types = snapshot.columnTypes.get(table);
2613
+ const known = types?.get(column) ?? types?.get(column.toLowerCase());
2614
+ if (known) return known;
2615
+ if (types) return null;
2616
+ }
2617
+ const cached = this.columnTypeCache.get(table)?.get(column);
2618
+ if (cached !== void 0) return cached;
2619
+ const schema = this.schemaName || "public";
2620
+ 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)`, [
2621
+ schema,
2622
+ table,
2623
+ column,
2624
+ column.toLowerCase()
2625
+ ]))?.data_type ?? null;
2626
+ if (dataType) {
2627
+ if (snapshot) {
2628
+ let snapshotTypes = snapshot.columnTypes.get(table);
2629
+ if (!snapshotTypes) {
2630
+ snapshotTypes = /* @__PURE__ */ new Map();
2631
+ snapshot.columnTypes.set(table, snapshotTypes);
2632
+ }
2633
+ snapshotTypes.set(column, dataType);
2634
+ }
2635
+ let types = this.columnTypeCache.get(table);
2636
+ if (!types) {
2637
+ types = /* @__PURE__ */ new Map();
2638
+ this.columnTypeCache.set(table, types);
2639
+ }
2640
+ types.set(column, dataType);
2641
+ }
2642
+ return dataType;
2643
+ }
2644
+ /**
2586
2645
  * Prepares values for insertion, handling JSONB columns by stringifying them
2587
2646
  */
2588
2647
  prepareValuesForInsert(record, tableName) {
@@ -2781,6 +2840,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
2781
2840
  }, error);
2782
2841
  } finally {
2783
2842
  this.tableColumnsCache.delete(tableName);
2843
+ this.columnTypeCache.delete(tableName);
2784
2844
  }
2785
2845
  }
2786
2846
  async setupTimestampTriggers(tableName) {
@@ -3103,6 +3163,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3103
3163
  }, error);
3104
3164
  } finally {
3105
3165
  this.tableColumnsCache.delete(tableName);
3166
+ this.columnTypeCache.delete(tableName);
3106
3167
  }
3107
3168
  }
3108
3169
  async load({ tableName, keys }) {
@@ -3166,6 +3227,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3166
3227
  }, error);
3167
3228
  } finally {
3168
3229
  this.tableColumnsCache.delete(tableName);
3230
+ this.columnTypeCache.delete(tableName);
3169
3231
  }
3170
3232
  }
3171
3233
  async createIndex(options) {
@@ -19298,6 +19360,29 @@ function getTableName({ indexName, schemaName }) {
19298
19360
  const quotedIndexName = `"${indexName}"`;
19299
19361
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
19300
19362
  }
19363
+ /** Base name (before any schema prefix) of the expression index backing the status filter. */
19364
+ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_createdat_idx";
19365
+ /**
19366
+ * Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
19367
+ * stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
19368
+ * no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
19369
+ */
19370
+ function workflowSnapshotStatusIndexName(schemaName) {
19371
+ return buildConstraintName({
19372
+ baseName: WORKFLOW_SNAPSHOT_STATUS_INDEX,
19373
+ schemaName: schemaName && schemaName !== "public" ? schemaName : void 0
19374
+ });
19375
+ }
19376
+ /**
19377
+ * Expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)` so
19378
+ * listWorkflowRuns() status filters can use an index instead of scanning every snapshot.
19379
+ */
19380
+ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
19381
+ return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
19382
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
19383
+ schemaName: getSchemaName(schemaName)
19384
+ })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
19385
+ }
19301
19386
  /**
19302
19387
  * Sanitizes JSON string for PostgreSQL jsonb:
19303
19388
  * - Removes problematic Unicode sequences:
@@ -19358,33 +19443,55 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
19358
19443
  updatedAt: new Date(row.updatedAtZ || row.updatedAt)
19359
19444
  };
19360
19445
  }
19446
+ static getDefaultIndexDefs(schemaPrefix) {
19447
+ return [{
19448
+ name: `${schemaPrefix}mastra_workflow_snapshot_name_createdat_idx`,
19449
+ table: TABLE_WORKFLOW_SNAPSHOT,
19450
+ columns: ["workflow_name", "createdAt DESC"]
19451
+ }];
19452
+ }
19361
19453
  /**
19362
19454
  * Returns all DDL statements for this domain: table with unique constraint.
19363
19455
  * Used by exportSchemas to produce a complete, reproducible schema export.
19364
19456
  */
19365
19457
  static getExportDDL(schemaName) {
19366
19458
  const statements = [];
19459
+ const parsedSchema = schemaName ? parseSqlIdentifier(schemaName, "schema name") : "";
19460
+ const schemaPrefix = parsedSchema && parsedSchema !== "public" ? `${parsedSchema}_` : "";
19367
19461
  statements.push(generateTableSQL({
19368
19462
  tableName: TABLE_WORKFLOW_SNAPSHOT,
19369
19463
  schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
19370
19464
  schemaName,
19371
19465
  includeAllConstraints: true
19372
19466
  }));
19467
+ for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
19468
+ statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
19373
19469
  return statements;
19374
19470
  }
19375
19471
  /**
19376
19472
  * Returns default index definitions for the workflows domain tables.
19377
- * Currently no default indexes are defined for workflows.
19378
19473
  */
19379
19474
  getDefaultIndexDefinitions() {
19380
- return [];
19475
+ const schemaPrefix = this.#schema !== "public" ? `${this.#schema}_` : "";
19476
+ return WorkflowsPG.getDefaultIndexDefs(schemaPrefix);
19381
19477
  }
19382
19478
  /**
19383
19479
  * Creates default indexes for optimal query performance.
19384
- * Currently no default indexes are defined for workflows.
19385
19480
  */
19386
19481
  async createDefaultIndexes() {
19387
19482
  if (this.#skipDefaultIndexes) return;
19483
+ for (const indexDef of this.getDefaultIndexDefinitions()) try {
19484
+ await this.#db.createIndex(indexDef);
19485
+ } catch (error) {
19486
+ this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
19487
+ }
19488
+ if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
19489
+ const indexName = workflowSnapshotStatusIndexName(this.#schema);
19490
+ try {
19491
+ await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema));
19492
+ } catch (error) {
19493
+ this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
19494
+ }
19388
19495
  }
19389
19496
  async init() {
19390
19497
  await this.#db.createTable({
@@ -19674,7 +19781,8 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
19674
19781
  paramIndex++;
19675
19782
  }
19676
19783
  if (status) {
19677
- conditions.push(`regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status' = $${paramIndex}`);
19784
+ const statusExpr = await this.#db.getColumnType(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'`;
19785
+ conditions.push(`${statusExpr} = $${paramIndex}`);
19678
19786
  values.push(status);
19679
19787
  paramIndex++;
19680
19788
  }