@mastra/pg 0.15.2 → 0.15.3-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
@@ -5,7 +5,7 @@ import { Mutex } from 'async-mutex';
5
5
  import pg from 'pg';
6
6
  import xxhash from 'xxhash-wasm';
7
7
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
8
- import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, ScoresStorage, TABLE_SCORERS, TracesStorage, safelyParseJSON, TABLE_TRACES, WorkflowsStorage, LegacyEvalsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES, TABLE_EVALS, TABLE_THREADS, TABLE_MESSAGES } from '@mastra/core/storage';
8
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_EVALS, ScoresStorage, TABLE_SCORERS, TracesStorage, safelyParseJSON, WorkflowsStorage, LegacyEvalsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES } from '@mastra/core/storage';
9
9
  import pgPromise from 'pg-promise';
10
10
  import { MessageList } from '@mastra/core/agent';
11
11
 
@@ -2366,6 +2366,303 @@ var StoreOperationsPG = class extends StoreOperations {
2366
2366
  );
2367
2367
  }
2368
2368
  }
2369
+ /**
2370
+ * Create a new index on a table
2371
+ */
2372
+ async createIndex(options) {
2373
+ try {
2374
+ const {
2375
+ name,
2376
+ table,
2377
+ columns,
2378
+ unique = false,
2379
+ concurrent = true,
2380
+ where,
2381
+ method = "btree",
2382
+ opclass,
2383
+ storage,
2384
+ tablespace
2385
+ } = options;
2386
+ const schemaName = this.schemaName || "public";
2387
+ const fullTableName = getTableName({
2388
+ indexName: table,
2389
+ schemaName: getSchemaName(this.schemaName)
2390
+ });
2391
+ const indexExists = await this.client.oneOrNone(
2392
+ `SELECT 1 FROM pg_indexes
2393
+ WHERE indexname = $1
2394
+ AND schemaname = $2`,
2395
+ [name, schemaName]
2396
+ );
2397
+ if (indexExists) {
2398
+ return;
2399
+ }
2400
+ const uniqueStr = unique ? "UNIQUE " : "";
2401
+ const concurrentStr = concurrent ? "CONCURRENTLY " : "";
2402
+ const methodStr = method !== "btree" ? `USING ${method} ` : "";
2403
+ const columnsStr = columns.map((col) => {
2404
+ if (col.includes(" DESC") || col.includes(" ASC")) {
2405
+ const [colName, ...modifiers] = col.split(" ");
2406
+ if (!colName) {
2407
+ throw new Error(`Invalid column specification: ${col}`);
2408
+ }
2409
+ const quotedCol2 = `"${parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
2410
+ return opclass ? `${quotedCol2} ${opclass}` : quotedCol2;
2411
+ }
2412
+ const quotedCol = `"${parseSqlIdentifier(col, "column name")}"`;
2413
+ return opclass ? `${quotedCol} ${opclass}` : quotedCol;
2414
+ }).join(", ");
2415
+ const whereStr = where ? ` WHERE ${where}` : "";
2416
+ const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
2417
+ let withStr = "";
2418
+ if (storage && Object.keys(storage).length > 0) {
2419
+ const storageParams = Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ");
2420
+ withStr = ` WITH (${storageParams})`;
2421
+ }
2422
+ const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${name} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
2423
+ await this.client.none(sql);
2424
+ } catch (error) {
2425
+ if (error instanceof Error && error.message.includes("CONCURRENTLY")) {
2426
+ const retryOptions = { ...options, concurrent: false };
2427
+ return this.createIndex(retryOptions);
2428
+ }
2429
+ throw new MastraError(
2430
+ {
2431
+ id: "MASTRA_STORAGE_PG_INDEX_CREATE_FAILED",
2432
+ domain: ErrorDomain.STORAGE,
2433
+ category: ErrorCategory.THIRD_PARTY,
2434
+ details: {
2435
+ indexName: options.name,
2436
+ tableName: options.table
2437
+ }
2438
+ },
2439
+ error
2440
+ );
2441
+ }
2442
+ }
2443
+ /**
2444
+ * Drop an existing index
2445
+ */
2446
+ async dropIndex(indexName) {
2447
+ try {
2448
+ const schemaName = this.schemaName || "public";
2449
+ const indexExists = await this.client.oneOrNone(
2450
+ `SELECT 1 FROM pg_indexes
2451
+ WHERE indexname = $1
2452
+ AND schemaname = $2`,
2453
+ [indexName, schemaName]
2454
+ );
2455
+ if (!indexExists) {
2456
+ return;
2457
+ }
2458
+ const sql = `DROP INDEX IF EXISTS ${getSchemaName(this.schemaName)}.${indexName}`;
2459
+ await this.client.none(sql);
2460
+ } catch (error) {
2461
+ throw new MastraError(
2462
+ {
2463
+ id: "MASTRA_STORAGE_PG_INDEX_DROP_FAILED",
2464
+ domain: ErrorDomain.STORAGE,
2465
+ category: ErrorCategory.THIRD_PARTY,
2466
+ details: {
2467
+ indexName
2468
+ }
2469
+ },
2470
+ error
2471
+ );
2472
+ }
2473
+ }
2474
+ /**
2475
+ * List indexes for a specific table or all tables
2476
+ */
2477
+ async listIndexes(tableName) {
2478
+ try {
2479
+ const schemaName = this.schemaName || "public";
2480
+ let query;
2481
+ let params;
2482
+ if (tableName) {
2483
+ query = `
2484
+ SELECT
2485
+ i.indexname as name,
2486
+ i.tablename as table,
2487
+ i.indexdef as definition,
2488
+ ix.indisunique as is_unique,
2489
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2490
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
2491
+ FROM pg_indexes i
2492
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2493
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2494
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2495
+ WHERE i.schemaname = $1
2496
+ AND i.tablename = $2
2497
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
2498
+ `;
2499
+ params = [schemaName, tableName];
2500
+ } else {
2501
+ query = `
2502
+ SELECT
2503
+ i.indexname as name,
2504
+ i.tablename as table,
2505
+ i.indexdef as definition,
2506
+ ix.indisunique as is_unique,
2507
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2508
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
2509
+ FROM pg_indexes i
2510
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2511
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2512
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2513
+ WHERE i.schemaname = $1
2514
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
2515
+ `;
2516
+ params = [schemaName];
2517
+ }
2518
+ const results = await this.client.manyOrNone(query, params);
2519
+ return results.map((row) => {
2520
+ let columns = [];
2521
+ if (typeof row.columns === "string" && row.columns.startsWith("{") && row.columns.endsWith("}")) {
2522
+ const arrayContent = row.columns.slice(1, -1);
2523
+ columns = arrayContent ? arrayContent.split(",") : [];
2524
+ } else if (Array.isArray(row.columns)) {
2525
+ columns = row.columns;
2526
+ }
2527
+ return {
2528
+ name: row.name,
2529
+ table: row.table,
2530
+ columns,
2531
+ unique: row.is_unique || false,
2532
+ size: row.size || "0",
2533
+ definition: row.definition || ""
2534
+ };
2535
+ });
2536
+ } catch (error) {
2537
+ throw new MastraError(
2538
+ {
2539
+ id: "MASTRA_STORAGE_PG_INDEX_LIST_FAILED",
2540
+ domain: ErrorDomain.STORAGE,
2541
+ category: ErrorCategory.THIRD_PARTY,
2542
+ details: tableName ? {
2543
+ tableName
2544
+ } : {}
2545
+ },
2546
+ error
2547
+ );
2548
+ }
2549
+ }
2550
+ /**
2551
+ * Creates automatic indexes for optimal query performance
2552
+ * These composite indexes cover both filtering and sorting in single index
2553
+ */
2554
+ async createAutomaticIndexes() {
2555
+ try {
2556
+ const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
2557
+ const indexes = [
2558
+ // Composite index for threads (filter + sort)
2559
+ {
2560
+ name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
2561
+ table: TABLE_THREADS,
2562
+ columns: ["resourceId", "createdAt DESC"]
2563
+ },
2564
+ // Composite index for messages (filter + sort)
2565
+ {
2566
+ name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
2567
+ table: TABLE_MESSAGES,
2568
+ columns: ["thread_id", "createdAt DESC"]
2569
+ },
2570
+ // Composite index for traces (filter + sort)
2571
+ {
2572
+ name: `${schemaPrefix}mastra_traces_name_starttime_idx`,
2573
+ table: TABLE_TRACES,
2574
+ columns: ["name", "startTime DESC"]
2575
+ },
2576
+ // Composite index for evals (filter + sort)
2577
+ {
2578
+ name: `${schemaPrefix}mastra_evals_agent_name_created_at_idx`,
2579
+ table: TABLE_EVALS,
2580
+ columns: ["agent_name", "created_at DESC"]
2581
+ }
2582
+ ];
2583
+ for (const indexOptions of indexes) {
2584
+ try {
2585
+ await this.createIndex(indexOptions);
2586
+ } catch (error) {
2587
+ this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
2588
+ }
2589
+ }
2590
+ } catch (error) {
2591
+ throw new MastraError(
2592
+ {
2593
+ id: "MASTRA_STORAGE_PG_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
2594
+ domain: ErrorDomain.STORAGE,
2595
+ category: ErrorCategory.THIRD_PARTY
2596
+ },
2597
+ error
2598
+ );
2599
+ }
2600
+ }
2601
+ /**
2602
+ * Get detailed statistics for a specific index
2603
+ */
2604
+ async describeIndex(indexName) {
2605
+ try {
2606
+ const schemaName = this.schemaName || "public";
2607
+ const query = `
2608
+ SELECT
2609
+ i.indexname as name,
2610
+ i.tablename as table,
2611
+ i.indexdef as definition,
2612
+ ix.indisunique as is_unique,
2613
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2614
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
2615
+ am.amname as method,
2616
+ s.idx_scan as scans,
2617
+ s.idx_tup_read as tuples_read,
2618
+ s.idx_tup_fetch as tuples_fetched
2619
+ FROM pg_indexes i
2620
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2621
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2622
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2623
+ JOIN pg_am am ON c.relam = am.oid
2624
+ LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
2625
+ WHERE i.schemaname = $1
2626
+ AND i.indexname = $2
2627
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid, am.amname, s.idx_scan, s.idx_tup_read, s.idx_tup_fetch
2628
+ `;
2629
+ const result = await this.client.oneOrNone(query, [schemaName, indexName]);
2630
+ if (!result) {
2631
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
2632
+ }
2633
+ let columns = [];
2634
+ if (typeof result.columns === "string" && result.columns.startsWith("{") && result.columns.endsWith("}")) {
2635
+ const arrayContent = result.columns.slice(1, -1);
2636
+ columns = arrayContent ? arrayContent.split(",") : [];
2637
+ } else if (Array.isArray(result.columns)) {
2638
+ columns = result.columns;
2639
+ }
2640
+ return {
2641
+ name: result.name,
2642
+ table: result.table,
2643
+ columns,
2644
+ unique: result.is_unique || false,
2645
+ size: result.size || "0",
2646
+ definition: result.definition || "",
2647
+ method: result.method || "btree",
2648
+ scans: parseInt(result.scans) || 0,
2649
+ tuples_read: parseInt(result.tuples_read) || 0,
2650
+ tuples_fetched: parseInt(result.tuples_fetched) || 0
2651
+ };
2652
+ } catch (error) {
2653
+ throw new MastraError(
2654
+ {
2655
+ id: "MASTRA_STORAGE_PG_INDEX_DESCRIBE_FAILED",
2656
+ domain: ErrorDomain.STORAGE,
2657
+ category: ErrorCategory.THIRD_PARTY,
2658
+ details: {
2659
+ indexName
2660
+ }
2661
+ },
2662
+ error
2663
+ );
2664
+ }
2665
+ }
2369
2666
  };
2370
2667
  function transformScoreRow(row) {
2371
2668
  return {
@@ -2800,16 +3097,17 @@ var WorkflowsPG = class extends WorkflowsStorage {
2800
3097
  async persistWorkflowSnapshot({
2801
3098
  workflowName,
2802
3099
  runId,
3100
+ resourceId,
2803
3101
  snapshot
2804
3102
  }) {
2805
3103
  try {
2806
3104
  const now = (/* @__PURE__ */ new Date()).toISOString();
2807
3105
  await this.client.none(
2808
- `INSERT INTO ${getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })} (workflow_name, run_id, snapshot, "createdAt", "updatedAt")
2809
- VALUES ($1, $2, $3, $4, $5)
3106
+ `INSERT INTO ${getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
3107
+ VALUES ($1, $2, $3, $4, $5, $6)
2810
3108
  ON CONFLICT (workflow_name, run_id) DO UPDATE
2811
- SET snapshot = $3, "updatedAt" = $5`,
2812
- [workflowName, runId, JSON.stringify(snapshot), now, now]
3109
+ SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6`,
3110
+ [workflowName, runId, resourceId, JSON.stringify(snapshot), now, now]
2813
3111
  );
2814
3112
  } catch (error) {
2815
3113
  throw new MastraError(
@@ -2971,37 +3269,68 @@ var PostgresStore = class extends MastraStorage {
2971
3269
  isConnected = false;
2972
3270
  stores;
2973
3271
  constructor(config) {
3272
+ const isConnectionStringConfig = (cfg) => {
3273
+ return "connectionString" in cfg;
3274
+ };
3275
+ const isHostConfig = (cfg) => {
3276
+ return "host" in cfg && "database" in cfg && "user" in cfg && "password" in cfg;
3277
+ };
3278
+ const isCloudSqlConfig = (cfg) => {
3279
+ return "stream" in cfg || "password" in cfg && typeof cfg.password === "function";
3280
+ };
2974
3281
  try {
2975
- if ("connectionString" in config) {
3282
+ if (isConnectionStringConfig(config)) {
2976
3283
  if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
2977
3284
  throw new Error(
2978
3285
  "PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
2979
3286
  );
2980
3287
  }
2981
- } else {
3288
+ } else if (isCloudSqlConfig(config)) ; else if (isHostConfig(config)) {
2982
3289
  const required = ["host", "database", "user", "password"];
2983
3290
  for (const key of required) {
2984
- if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
3291
+ if (!config[key] || typeof config[key] !== "string" || config[key].trim() === "") {
2985
3292
  throw new Error(
2986
3293
  `PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
2987
3294
  );
2988
3295
  }
2989
3296
  }
3297
+ } else {
3298
+ throw new Error(
3299
+ "PostgresStore: invalid config. Provide either {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
3300
+ );
2990
3301
  }
2991
3302
  super({ name: "PostgresStore" });
2992
3303
  this.schema = config.schemaName || "public";
2993
- this.#config = {
2994
- max: config.max,
2995
- idleTimeoutMillis: config.idleTimeoutMillis,
2996
- ...`connectionString` in config ? { connectionString: config.connectionString } : {
3304
+ if (isConnectionStringConfig(config)) {
3305
+ this.#config = {
3306
+ connectionString: config.connectionString,
3307
+ max: config.max,
3308
+ idleTimeoutMillis: config.idleTimeoutMillis
3309
+ };
3310
+ } else if (isCloudSqlConfig(config)) {
3311
+ this.#config = {
3312
+ ...config,
3313
+ max: config.max,
3314
+ idleTimeoutMillis: config.idleTimeoutMillis
3315
+ };
3316
+ } else if (isHostConfig(config)) {
3317
+ this.#config = {
2997
3318
  host: config.host,
2998
3319
  port: config.port,
2999
3320
  database: config.database,
3000
3321
  user: config.user,
3001
3322
  password: config.password,
3002
- ssl: config.ssl
3003
- }
3004
- };
3323
+ ssl: config.ssl,
3324
+ max: config.max,
3325
+ idleTimeoutMillis: config.idleTimeoutMillis
3326
+ };
3327
+ } else {
3328
+ this.#config = {
3329
+ ...config,
3330
+ max: config.max,
3331
+ idleTimeoutMillis: config.idleTimeoutMillis
3332
+ };
3333
+ }
3005
3334
  this.stores = {};
3006
3335
  } catch (e) {
3007
3336
  throw new MastraError(
@@ -3037,6 +3366,11 @@ var PostgresStore = class extends MastraStorage {
3037
3366
  memory
3038
3367
  };
3039
3368
  await super.init();
3369
+ try {
3370
+ await operations.createAutomaticIndexes();
3371
+ } catch (indexError) {
3372
+ console.warn("Failed to create indexes:", indexError);
3373
+ }
3040
3374
  } catch (error) {
3041
3375
  this.isConnected = false;
3042
3376
  throw new MastraError(
@@ -3067,7 +3401,9 @@ var PostgresStore = class extends MastraStorage {
3067
3401
  resourceWorkingMemory: true,
3068
3402
  hasColumn: true,
3069
3403
  createTable: true,
3070
- deleteMessages: true
3404
+ deleteMessages: true,
3405
+ aiTracing: false,
3406
+ indexManagement: true
3071
3407
  };
3072
3408
  }
3073
3409
  /** @deprecated use getEvals instead */
@@ -3203,9 +3539,10 @@ var PostgresStore = class extends MastraStorage {
3203
3539
  async persistWorkflowSnapshot({
3204
3540
  workflowName,
3205
3541
  runId,
3542
+ resourceId,
3206
3543
  snapshot
3207
3544
  }) {
3208
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
3545
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
3209
3546
  }
3210
3547
  async loadWorkflowSnapshot({
3211
3548
  workflowName,