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

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
@@ -541,6 +541,7 @@ function buildFilterQuery(filter, minScore, topK) {
541
541
  }
542
542
  //#endregion
543
543
  //#region src/vector/index.ts
544
+ const MAX_UPSERT_ROWS_PER_STATEMENT = Math.floor(65535 / 3);
544
545
  var PgVector = class extends MastraVector {
545
546
  pool;
546
547
  describeIndexCache = /* @__PURE__ */ new Map();
@@ -933,23 +934,41 @@ var PgVector = class extends MastraVector {
933
934
  const indexInfo = await this.getIndexInfo({ indexName });
934
935
  const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
935
936
  const ops = this.getVectorOps(indexInfo.vectorType, indexInfo.metric ?? "cosine");
936
- for (let i = 0; i < vectors.length; i++) {
937
+ if (new Set(vectorIds).size !== vectorIds.length) for (let i = 0; i < vectors.length; i++) {
937
938
  const vectorStr = ops.formatVector(vectors[i], indexInfo.dimension);
938
939
  const query = `
939
- INSERT INTO ${tableName} (vector_id, embedding, metadata)
940
- VALUES ($1, $2::${qualifiedVectorType}, $3::jsonb)
941
- ON CONFLICT (vector_id)
942
- DO UPDATE SET
943
- embedding = $2::${qualifiedVectorType},
944
- metadata = $3::jsonb
945
- RETURNING embedding::text
946
- `;
940
+ INSERT INTO ${tableName} (vector_id, embedding, metadata)
941
+ VALUES ($1, $2::${qualifiedVectorType}, $3::jsonb)
942
+ ON CONFLICT (vector_id)
943
+ DO UPDATE SET
944
+ embedding = $2::${qualifiedVectorType},
945
+ metadata = $3::jsonb
946
+ `;
947
947
  await client.query(query, [
948
948
  vectorIds[i],
949
949
  vectorStr,
950
950
  JSON.stringify(metadata?.[i] || {})
951
951
  ]);
952
952
  }
953
+ else for (let start = 0; start < vectors.length; start += MAX_UPSERT_ROWS_PER_STATEMENT) {
954
+ const end = Math.min(start + MAX_UPSERT_ROWS_PER_STATEMENT, vectors.length);
955
+ const rows = [];
956
+ const values = [];
957
+ for (let i = start; i < end; i++) {
958
+ const base = values.length;
959
+ rows.push(`($${base + 1}, $${base + 2}::${qualifiedVectorType}, $${base + 3}::jsonb)`);
960
+ values.push(vectorIds[i], ops.formatVector(vectors[i], indexInfo.dimension), JSON.stringify(metadata?.[i] || {}));
961
+ }
962
+ const query = `
963
+ INSERT INTO ${tableName} (vector_id, embedding, metadata)
964
+ VALUES ${rows.join(", ")}
965
+ ON CONFLICT (vector_id)
966
+ DO UPDATE SET
967
+ embedding = EXCLUDED.embedding,
968
+ metadata = EXCLUDED.metadata
969
+ `;
970
+ await client.query(query, values);
971
+ }
953
972
  await client.query("COMMIT");
954
973
  this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
955
974
  indexName,
@@ -2152,7 +2171,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2152
2171
  const schema = schemaName || "public";
2153
2172
  const [tableRows, columnRows, indexRows] = await Promise.all([
2154
2173
  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
2174
+ client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS data_type
2156
2175
  FROM pg_catalog.pg_class c
2157
2176
  JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2158
2177
  JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
@@ -2167,6 +2186,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2167
2186
  WHERE n.nspname = $1`, [schema])
2168
2187
  ]);
2169
2188
  const columns = /* @__PURE__ */ new Map();
2189
+ const columnTypes = /* @__PURE__ */ new Map();
2170
2190
  for (const row of columnRows) {
2171
2191
  let set = columns.get(row.table_name);
2172
2192
  if (!set) {
@@ -2174,6 +2194,12 @@ async function loadSchemaSnapshot(client, schemaName) {
2174
2194
  columns.set(row.table_name, set);
2175
2195
  }
2176
2196
  set.add(row.column_name);
2197
+ let types = columnTypes.get(row.table_name);
2198
+ if (!types) {
2199
+ types = /* @__PURE__ */ new Map();
2200
+ columnTypes.set(row.table_name, types);
2201
+ }
2202
+ types.set(row.column_name, row.data_type);
2177
2203
  }
2178
2204
  const indexes = /* @__PURE__ */ new Set();
2179
2205
  const replicaIdentityIndexes = /* @__PURE__ */ new Set();
@@ -2187,6 +2213,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2187
2213
  schemaName: schema,
2188
2214
  tables: new Set(tableRows.map((r) => r.tablename)),
2189
2215
  columns,
2216
+ columnTypes,
2190
2217
  indexes,
2191
2218
  replicaIdentityIndexes,
2192
2219
  primaryKeyIndexes
@@ -2440,6 +2467,8 @@ var PgDB = class extends MastraBase {
2440
2467
  skipDefaultIndexes;
2441
2468
  /** Cache of actual table columns: tableName -> Set<columnName> */
2442
2469
  tableColumnsCache = /* @__PURE__ */ new Map();
2470
+ /** Cache of column Postgres data types: tableName -> columnName -> data_type */
2471
+ columnTypeCache = /* @__PURE__ */ new Map();
2443
2472
  constructor(config) {
2444
2473
  super({
2445
2474
  component: "STORAGE",
@@ -2514,7 +2543,9 @@ var PgDB = class extends MastraBase {
2514
2543
  }
2515
2544
  }
2516
2545
  this.tableColumnsCache.delete(oldName);
2546
+ this.columnTypeCache.delete(oldName);
2517
2547
  this.tableColumnsCache.delete(newName);
2548
+ this.columnTypeCache.delete(newName);
2518
2549
  }
2519
2550
  /**
2520
2551
  * Records an out-of-band `DROP TABLE` in the init snapshot. See
@@ -2530,8 +2561,10 @@ var PgDB = class extends MastraBase {
2530
2561
  if (snapshot) {
2531
2562
  snapshot.tables.delete(tableName);
2532
2563
  snapshot.columns.delete(tableName);
2564
+ snapshot.columnTypes.delete(tableName);
2533
2565
  }
2534
2566
  this.tableColumnsCache.delete(tableName);
2567
+ this.columnTypeCache.delete(tableName);
2535
2568
  }
2536
2569
  /**
2537
2570
  * Records an out-of-band `ALTER TABLE … ADD COLUMN` in the init snapshot.
@@ -2541,6 +2574,7 @@ var PgDB = class extends MastraBase {
2541
2574
  const snapshot = this.schemaSnapshot;
2542
2575
  if (snapshot) this.snapshotColumns(snapshot, tableName).add(column);
2543
2576
  this.tableColumnsCache.delete(tableName);
2577
+ this.columnTypeCache.delete(tableName);
2544
2578
  }
2545
2579
  /**
2546
2580
  * Gets the set of column names that actually exist in the database table.
@@ -2583,6 +2617,50 @@ var PgDB = class extends MastraBase {
2583
2617
  ]);
2584
2618
  }
2585
2619
  /**
2620
+ * Returns the Postgres data type of a column (e.g. `jsonb`, `json`, `text`),
2621
+ * or null when the table or column does not exist.
2622
+ *
2623
+ * Answered from the init snapshot when one is installed, so a warm `init()`
2624
+ * issues no catalog probe. Outside init, results are cached per instance and
2625
+ * the cache is invalidated alongside {@link tableColumnsCache} whenever DDL
2626
+ * changes a table.
2627
+ */
2628
+ async getColumnType(table, column) {
2629
+ const snapshot = this.schemaSnapshot;
2630
+ if (snapshot) {
2631
+ const types = snapshot.columnTypes.get(table);
2632
+ const known = types?.get(column) ?? types?.get(column.toLowerCase());
2633
+ if (known) return known;
2634
+ if (types) return null;
2635
+ }
2636
+ const cached = this.columnTypeCache.get(table)?.get(column);
2637
+ if (cached !== void 0) return cached;
2638
+ const schema = this.schemaName || "public";
2639
+ 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)`, [
2640
+ schema,
2641
+ table,
2642
+ column,
2643
+ column.toLowerCase()
2644
+ ]))?.data_type ?? null;
2645
+ if (dataType) {
2646
+ if (snapshot) {
2647
+ let snapshotTypes = snapshot.columnTypes.get(table);
2648
+ if (!snapshotTypes) {
2649
+ snapshotTypes = /* @__PURE__ */ new Map();
2650
+ snapshot.columnTypes.set(table, snapshotTypes);
2651
+ }
2652
+ snapshotTypes.set(column, dataType);
2653
+ }
2654
+ let types = this.columnTypeCache.get(table);
2655
+ if (!types) {
2656
+ types = /* @__PURE__ */ new Map();
2657
+ this.columnTypeCache.set(table, types);
2658
+ }
2659
+ types.set(column, dataType);
2660
+ }
2661
+ return dataType;
2662
+ }
2663
+ /**
2586
2664
  * Prepares values for insertion, handling JSONB columns by stringifying them
2587
2665
  */
2588
2666
  prepareValuesForInsert(record, tableName) {
@@ -2781,6 +2859,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
2781
2859
  }, error);
2782
2860
  } finally {
2783
2861
  this.tableColumnsCache.delete(tableName);
2862
+ this.columnTypeCache.delete(tableName);
2784
2863
  }
2785
2864
  }
2786
2865
  async setupTimestampTriggers(tableName) {
@@ -3103,6 +3182,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3103
3182
  }, error);
3104
3183
  } finally {
3105
3184
  this.tableColumnsCache.delete(tableName);
3185
+ this.columnTypeCache.delete(tableName);
3106
3186
  }
3107
3187
  }
3108
3188
  async load({ tableName, keys }) {
@@ -3166,6 +3246,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3166
3246
  }, error);
3167
3247
  } finally {
3168
3248
  this.tableColumnsCache.delete(tableName);
3249
+ this.columnTypeCache.delete(tableName);
3169
3250
  }
3170
3251
  }
3171
3252
  async createIndex(options) {
@@ -3717,6 +3798,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
3717
3798
  "workspace",
3718
3799
  "skills",
3719
3800
  "skillsFormat",
3801
+ "durable",
3720
3802
  "browser",
3721
3803
  "toolProviders"
3722
3804
  ]
@@ -4196,10 +4278,10 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4196
4278
  "defaultOptions", workflows, agents, "integrationTools", "toolProviders",
4197
4279
  "inputProcessors", "outputProcessors", memory, scorers,
4198
4280
  "mcpClients", "requestContextSchema", workspace, skills, "skillsFormat",
4199
- browser,
4281
+ durable, browser,
4200
4282
  "changedFields", "changeMessage",
4201
4283
  "createdAt", "createdAtZ"
4202
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27)`, [
4284
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)`, [
4203
4285
  input.id,
4204
4286
  input.agentId,
4205
4287
  input.versionNumber,
@@ -4222,6 +4304,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4222
4304
  input.workspace ? JSON.stringify(input.workspace) : null,
4223
4305
  input.skills ? JSON.stringify(input.skills) : null,
4224
4306
  input.skillsFormat ?? null,
4307
+ input.durable !== void 0 ? JSON.stringify(input.durable) : null,
4225
4308
  input.browser ? JSON.stringify(input.browser) : null,
4226
4309
  input.changedFields ? JSON.stringify(input.changedFields) : null,
4227
4310
  input.changeMessage ?? null,
@@ -4450,6 +4533,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4450
4533
  workspace: parseJsonResilient(row.workspace, "workspace"),
4451
4534
  skills: parseJsonResilient(row.skills, "skills"),
4452
4535
  skillsFormat: row.skillsFormat,
4536
+ durable: parseJsonResilient(row.durable, "durable"),
4453
4537
  browser: parseJsonResilient(row.browser, "browser"),
4454
4538
  changedFields: parseJsonResilient(row.changedFields, "changedFields"),
4455
4539
  changeMessage: row.changeMessage,
@@ -19298,6 +19382,29 @@ function getTableName({ indexName, schemaName }) {
19298
19382
  const quotedIndexName = `"${indexName}"`;
19299
19383
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
19300
19384
  }
19385
+ /** Base name (before any schema prefix) of the expression index backing the status filter. */
19386
+ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_createdat_idx";
19387
+ /**
19388
+ * Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
19389
+ * stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
19390
+ * no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
19391
+ */
19392
+ function workflowSnapshotStatusIndexName(schemaName) {
19393
+ return buildConstraintName({
19394
+ baseName: WORKFLOW_SNAPSHOT_STATUS_INDEX,
19395
+ schemaName: schemaName && schemaName !== "public" ? schemaName : void 0
19396
+ });
19397
+ }
19398
+ /**
19399
+ * Expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)` so
19400
+ * listWorkflowRuns() status filters can use an index instead of scanning every snapshot.
19401
+ */
19402
+ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
19403
+ return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
19404
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
19405
+ schemaName: getSchemaName(schemaName)
19406
+ })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
19407
+ }
19301
19408
  /**
19302
19409
  * Sanitizes JSON string for PostgreSQL jsonb:
19303
19410
  * - Removes problematic Unicode sequences:
@@ -19380,6 +19487,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
19380
19487
  includeAllConstraints: true
19381
19488
  }));
19382
19489
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
19490
+ statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
19383
19491
  return statements;
19384
19492
  }
19385
19493
  /**
@@ -19399,6 +19507,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
19399
19507
  } catch (error) {
19400
19508
  this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
19401
19509
  }
19510
+ if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
19511
+ const indexName = workflowSnapshotStatusIndexName(this.#schema);
19512
+ try {
19513
+ await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema));
19514
+ } catch (error) {
19515
+ this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
19516
+ }
19402
19517
  }
19403
19518
  async init() {
19404
19519
  await this.#db.createTable({
@@ -19688,7 +19803,8 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
19688
19803
  paramIndex++;
19689
19804
  }
19690
19805
  if (status) {
19691
- conditions.push(`regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status' = $${paramIndex}`);
19806
+ 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'`;
19807
+ conditions.push(`${statusExpr} = $${paramIndex}`);
19692
19808
  values.push(status);
19693
19809
  paramIndex++;
19694
19810
  }