@mastra/pg 0.15.3-alpha.0 → 0.15.3-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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 0.15.3-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Add Cloud SQL + IAM authentication support to PostgresStore ([#7855](https://github.com/mastra-ai/mastra/pull/7855))
8
+
9
+ - Update peerdep of @mastra/core ([#7619](https://github.com/mastra-ai/mastra/pull/7619))
10
+
11
+ - Updated dependencies [[`a1bb887`](https://github.com/mastra-ai/mastra/commit/a1bb887e8bfae44230f487648da72e96ef824561), [`a0f5f1c`](https://github.com/mastra-ai/mastra/commit/a0f5f1ca39c3c5c6d26202e9fcab986b4fe14568), [`b356f5f`](https://github.com/mastra-ai/mastra/commit/b356f5f7566cb3edb755d91f00b72fc1420b2a37), [`f5ce05f`](https://github.com/mastra-ai/mastra/commit/f5ce05f831d42c69559bf4c0fdb46ccb920fc3a3), [`9f6f30f`](https://github.com/mastra-ai/mastra/commit/9f6f30f04ec6648bbca798ea8aad59317c40d8db), [`d706fad`](https://github.com/mastra-ai/mastra/commit/d706fad6e6e4b72357b18d229ba38e6c913c0e70), [`5c3768f`](https://github.com/mastra-ai/mastra/commit/5c3768fa959454232ad76715c381f4aac00c6881), [`8a3f5e4`](https://github.com/mastra-ai/mastra/commit/8a3f5e4212ec36b302957deb4bd47005ab598382)]:
12
+ - @mastra/core@0.17.0-alpha.3
13
+
14
+ ## 0.15.3-alpha.1
15
+
16
+ ### Patch Changes
17
+
18
+ - Postgresql Storage Query Index Performance: Adds index operations and automatic indexing for Postgresql ([#7757](https://github.com/mastra-ai/mastra/pull/7757))
19
+
20
+ - Updated dependencies [[`60c9cec`](https://github.com/mastra-ai/mastra/commit/60c9cec7048a79a87440f7840c383875bd710d93), [`897995e`](https://github.com/mastra-ai/mastra/commit/897995e630d572fe2891e7ede817938cabb43251)]:
21
+ - @mastra/core@0.16.4-alpha.2
22
+
3
23
  ## 0.15.3-alpha.0
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -160,6 +160,129 @@ Example filter:
160
160
  - `getMessages(threadId)`: Get all messages for a thread
161
161
  - `deleteMessages(messageIds)`: Delete specific messages
162
162
 
163
+ ## Index Management
164
+
165
+ The PostgreSQL store provides comprehensive index management capabilities to optimize query performance.
166
+
167
+ ### Automatic Performance Indexes
168
+
169
+ PostgreSQL storage automatically creates composite indexes during initialization for common query patterns:
170
+
171
+ - `mastra_threads_resourceid_createdat_idx`: (resourceId, createdAt DESC)
172
+ - `mastra_messages_thread_id_createdat_idx`: (thread_id, createdAt DESC)
173
+ - `mastra_traces_name_starttime_idx`: (name, startTime DESC)
174
+ - `mastra_evals_agent_name_created_at_idx`: (agent_name, created_at DESC)
175
+
176
+ These indexes significantly improve performance for filtered queries with sorting.
177
+
178
+ ### Creating Custom Indexes
179
+
180
+ Create additional indexes to optimize specific query patterns:
181
+
182
+ ```typescript
183
+ // Basic index for common queries
184
+ await store.createIndex({
185
+ name: 'idx_threads_resource',
186
+ table: 'mastra_threads',
187
+ columns: ['resourceId'],
188
+ });
189
+
190
+ // Composite index with sort order for filtering + sorting
191
+ await store.createIndex({
192
+ name: 'idx_messages_composite',
193
+ table: 'mastra_messages',
194
+ columns: ['thread_id', 'createdAt DESC'],
195
+ });
196
+
197
+ // GIN index for JSONB columns (fast JSON queries)
198
+ await store.createIndex({
199
+ name: 'idx_traces_attributes',
200
+ table: 'mastra_traces',
201
+ columns: ['attributes'],
202
+ method: 'gin',
203
+ });
204
+ ```
205
+
206
+ For more advanced use cases, you can also use:
207
+
208
+ - `unique: true` for unique constraints
209
+ - `where: 'condition'` for partial indexes
210
+ - `method: 'brin'` for time-series data
211
+ - `storage: { fillfactor: 90 }` for update-heavy tables
212
+ - `concurrent: true` for non-blocking creation (default)
213
+
214
+ ### Managing Indexes
215
+
216
+ ```typescript
217
+ // List all indexes
218
+ const allIndexes = await store.listIndexes();
219
+
220
+ // List indexes for specific table
221
+ const threadIndexes = await store.listIndexes('mastra_threads');
222
+
223
+ // Get detailed statistics for an index
224
+ const stats = await store.describeIndex('idx_threads_resource');
225
+ console.log(stats);
226
+ // {
227
+ // name: 'idx_threads_resource',
228
+ // table: 'mastra_threads',
229
+ // columns: ['resourceId', 'createdAt'],
230
+ // unique: false,
231
+ // size: '128 KB',
232
+ // definition: 'CREATE INDEX idx_threads_resource...',
233
+ // method: 'btree',
234
+ // scans: 1542, // Number of index scans
235
+ // tuples_read: 45230, // Tuples read via index
236
+ // tuples_fetched: 12050 // Tuples fetched via index
237
+ // }
238
+
239
+ // Drop an index
240
+ await store.dropIndex('idx_threads_status');
241
+ ```
242
+
243
+ ### Index Types and Use Cases
244
+
245
+ | Index Type | Best For | Storage | Speed |
246
+ | ------------------- | --------------------------------------- | ---------- | -------------------------- |
247
+ | **btree** (default) | Range queries, sorting, general purpose | Moderate | Fast |
248
+ | **hash** | Equality comparisons only | Small | Very fast for `=` |
249
+ | **gin** | JSONB, arrays, full-text search | Large | Fast for contains |
250
+ | **gist** | Geometric data, full-text search | Moderate | Fast for nearest-neighbor |
251
+ | **spgist** | Non-balanced data, text patterns | Small | Fast for specific patterns |
252
+ | **brin** | Large tables with natural ordering | Very small | Fast for ranges |
253
+
254
+ ### Index Options
255
+
256
+ - `name` (required): Index name
257
+ - `table` (required): Table name
258
+ - `columns` (required): Array of column names (can include DESC/ASC)
259
+ - `unique`: Create unique index (default: false)
260
+ - `concurrent`: Non-blocking index creation (default: true)
261
+ - `where`: Partial index condition
262
+ - `method`: Index type ('btree' | 'hash' | 'gin' | 'gist' | 'spgist' | 'brin')
263
+ - `opclass`: Operator class for GIN/GIST indexes
264
+ - `storage`: Storage parameters (e.g., { fillfactor: 90 })
265
+ - `tablespace`: Tablespace name for index placement
266
+
267
+ ### Monitoring Index Performance
268
+
269
+ ```typescript
270
+ // Check index usage statistics
271
+ const stats = await store.describeIndex('idx_threads_resource');
272
+
273
+ // Identify unused indexes
274
+ if (stats.scans === 0) {
275
+ console.log(`Index ${stats.name} is unused - consider removing`);
276
+ await store.dropIndex(stats.name);
277
+ }
278
+
279
+ // Monitor index efficiency
280
+ const efficiency = stats.tuples_fetched / stats.tuples_read;
281
+ if (efficiency < 0.5) {
282
+ console.log(`Index ${stats.name} has low efficiency: ${efficiency}`);
283
+ }
284
+ ```
285
+
163
286
  ## Related Links
164
287
 
165
288
  - [pgvector Documentation](https://github.com/pgvector/pgvector)
package/dist/index.cjs CHANGED
@@ -2374,6 +2374,303 @@ var StoreOperationsPG = class extends storage.StoreOperations {
2374
2374
  );
2375
2375
  }
2376
2376
  }
2377
+ /**
2378
+ * Create a new index on a table
2379
+ */
2380
+ async createIndex(options) {
2381
+ try {
2382
+ const {
2383
+ name,
2384
+ table,
2385
+ columns,
2386
+ unique = false,
2387
+ concurrent = true,
2388
+ where,
2389
+ method = "btree",
2390
+ opclass,
2391
+ storage,
2392
+ tablespace
2393
+ } = options;
2394
+ const schemaName = this.schemaName || "public";
2395
+ const fullTableName = getTableName({
2396
+ indexName: table,
2397
+ schemaName: getSchemaName(this.schemaName)
2398
+ });
2399
+ const indexExists = await this.client.oneOrNone(
2400
+ `SELECT 1 FROM pg_indexes
2401
+ WHERE indexname = $1
2402
+ AND schemaname = $2`,
2403
+ [name, schemaName]
2404
+ );
2405
+ if (indexExists) {
2406
+ return;
2407
+ }
2408
+ const uniqueStr = unique ? "UNIQUE " : "";
2409
+ const concurrentStr = concurrent ? "CONCURRENTLY " : "";
2410
+ const methodStr = method !== "btree" ? `USING ${method} ` : "";
2411
+ const columnsStr = columns.map((col) => {
2412
+ if (col.includes(" DESC") || col.includes(" ASC")) {
2413
+ const [colName, ...modifiers] = col.split(" ");
2414
+ if (!colName) {
2415
+ throw new Error(`Invalid column specification: ${col}`);
2416
+ }
2417
+ const quotedCol2 = `"${utils.parseSqlIdentifier(colName, "column name")}" ${modifiers.join(" ")}`;
2418
+ return opclass ? `${quotedCol2} ${opclass}` : quotedCol2;
2419
+ }
2420
+ const quotedCol = `"${utils.parseSqlIdentifier(col, "column name")}"`;
2421
+ return opclass ? `${quotedCol} ${opclass}` : quotedCol;
2422
+ }).join(", ");
2423
+ const whereStr = where ? ` WHERE ${where}` : "";
2424
+ const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
2425
+ let withStr = "";
2426
+ if (storage && Object.keys(storage).length > 0) {
2427
+ const storageParams = Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ");
2428
+ withStr = ` WITH (${storageParams})`;
2429
+ }
2430
+ const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${name} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
2431
+ await this.client.none(sql);
2432
+ } catch (error$1) {
2433
+ if (error$1 instanceof Error && error$1.message.includes("CONCURRENTLY")) {
2434
+ const retryOptions = { ...options, concurrent: false };
2435
+ return this.createIndex(retryOptions);
2436
+ }
2437
+ throw new error.MastraError(
2438
+ {
2439
+ id: "MASTRA_STORAGE_PG_INDEX_CREATE_FAILED",
2440
+ domain: error.ErrorDomain.STORAGE,
2441
+ category: error.ErrorCategory.THIRD_PARTY,
2442
+ details: {
2443
+ indexName: options.name,
2444
+ tableName: options.table
2445
+ }
2446
+ },
2447
+ error$1
2448
+ );
2449
+ }
2450
+ }
2451
+ /**
2452
+ * Drop an existing index
2453
+ */
2454
+ async dropIndex(indexName) {
2455
+ try {
2456
+ const schemaName = this.schemaName || "public";
2457
+ const indexExists = await this.client.oneOrNone(
2458
+ `SELECT 1 FROM pg_indexes
2459
+ WHERE indexname = $1
2460
+ AND schemaname = $2`,
2461
+ [indexName, schemaName]
2462
+ );
2463
+ if (!indexExists) {
2464
+ return;
2465
+ }
2466
+ const sql = `DROP INDEX IF EXISTS ${getSchemaName(this.schemaName)}.${indexName}`;
2467
+ await this.client.none(sql);
2468
+ } catch (error$1) {
2469
+ throw new error.MastraError(
2470
+ {
2471
+ id: "MASTRA_STORAGE_PG_INDEX_DROP_FAILED",
2472
+ domain: error.ErrorDomain.STORAGE,
2473
+ category: error.ErrorCategory.THIRD_PARTY,
2474
+ details: {
2475
+ indexName
2476
+ }
2477
+ },
2478
+ error$1
2479
+ );
2480
+ }
2481
+ }
2482
+ /**
2483
+ * List indexes for a specific table or all tables
2484
+ */
2485
+ async listIndexes(tableName) {
2486
+ try {
2487
+ const schemaName = this.schemaName || "public";
2488
+ let query;
2489
+ let params;
2490
+ if (tableName) {
2491
+ query = `
2492
+ SELECT
2493
+ i.indexname as name,
2494
+ i.tablename as table,
2495
+ i.indexdef as definition,
2496
+ ix.indisunique as is_unique,
2497
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2498
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
2499
+ FROM pg_indexes i
2500
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2501
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2502
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2503
+ WHERE i.schemaname = $1
2504
+ AND i.tablename = $2
2505
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
2506
+ `;
2507
+ params = [schemaName, tableName];
2508
+ } else {
2509
+ query = `
2510
+ SELECT
2511
+ i.indexname as name,
2512
+ i.tablename as table,
2513
+ i.indexdef as definition,
2514
+ ix.indisunique as is_unique,
2515
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2516
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
2517
+ FROM pg_indexes i
2518
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2519
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2520
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2521
+ WHERE i.schemaname = $1
2522
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
2523
+ `;
2524
+ params = [schemaName];
2525
+ }
2526
+ const results = await this.client.manyOrNone(query, params);
2527
+ return results.map((row) => {
2528
+ let columns = [];
2529
+ if (typeof row.columns === "string" && row.columns.startsWith("{") && row.columns.endsWith("}")) {
2530
+ const arrayContent = row.columns.slice(1, -1);
2531
+ columns = arrayContent ? arrayContent.split(",") : [];
2532
+ } else if (Array.isArray(row.columns)) {
2533
+ columns = row.columns;
2534
+ }
2535
+ return {
2536
+ name: row.name,
2537
+ table: row.table,
2538
+ columns,
2539
+ unique: row.is_unique || false,
2540
+ size: row.size || "0",
2541
+ definition: row.definition || ""
2542
+ };
2543
+ });
2544
+ } catch (error$1) {
2545
+ throw new error.MastraError(
2546
+ {
2547
+ id: "MASTRA_STORAGE_PG_INDEX_LIST_FAILED",
2548
+ domain: error.ErrorDomain.STORAGE,
2549
+ category: error.ErrorCategory.THIRD_PARTY,
2550
+ details: tableName ? {
2551
+ tableName
2552
+ } : {}
2553
+ },
2554
+ error$1
2555
+ );
2556
+ }
2557
+ }
2558
+ /**
2559
+ * Creates automatic indexes for optimal query performance
2560
+ * These composite indexes cover both filtering and sorting in single index
2561
+ */
2562
+ async createAutomaticIndexes() {
2563
+ try {
2564
+ const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
2565
+ const indexes = [
2566
+ // Composite index for threads (filter + sort)
2567
+ {
2568
+ name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
2569
+ table: storage.TABLE_THREADS,
2570
+ columns: ["resourceId", "createdAt DESC"]
2571
+ },
2572
+ // Composite index for messages (filter + sort)
2573
+ {
2574
+ name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
2575
+ table: storage.TABLE_MESSAGES,
2576
+ columns: ["thread_id", "createdAt DESC"]
2577
+ },
2578
+ // Composite index for traces (filter + sort)
2579
+ {
2580
+ name: `${schemaPrefix}mastra_traces_name_starttime_idx`,
2581
+ table: storage.TABLE_TRACES,
2582
+ columns: ["name", "startTime DESC"]
2583
+ },
2584
+ // Composite index for evals (filter + sort)
2585
+ {
2586
+ name: `${schemaPrefix}mastra_evals_agent_name_created_at_idx`,
2587
+ table: storage.TABLE_EVALS,
2588
+ columns: ["agent_name", "created_at DESC"]
2589
+ }
2590
+ ];
2591
+ for (const indexOptions of indexes) {
2592
+ try {
2593
+ await this.createIndex(indexOptions);
2594
+ } catch (error) {
2595
+ this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
2596
+ }
2597
+ }
2598
+ } catch (error$1) {
2599
+ throw new error.MastraError(
2600
+ {
2601
+ id: "MASTRA_STORAGE_PG_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
2602
+ domain: error.ErrorDomain.STORAGE,
2603
+ category: error.ErrorCategory.THIRD_PARTY
2604
+ },
2605
+ error$1
2606
+ );
2607
+ }
2608
+ }
2609
+ /**
2610
+ * Get detailed statistics for a specific index
2611
+ */
2612
+ async describeIndex(indexName) {
2613
+ try {
2614
+ const schemaName = this.schemaName || "public";
2615
+ const query = `
2616
+ SELECT
2617
+ i.indexname as name,
2618
+ i.tablename as table,
2619
+ i.indexdef as definition,
2620
+ ix.indisunique as is_unique,
2621
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
2622
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
2623
+ am.amname as method,
2624
+ s.idx_scan as scans,
2625
+ s.idx_tup_read as tuples_read,
2626
+ s.idx_tup_fetch as tuples_fetched
2627
+ FROM pg_indexes i
2628
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2629
+ JOIN pg_index ix ON ix.indexrelid = c.oid
2630
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2631
+ JOIN pg_am am ON c.relam = am.oid
2632
+ LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
2633
+ WHERE i.schemaname = $1
2634
+ AND i.indexname = $2
2635
+ 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
2636
+ `;
2637
+ const result = await this.client.oneOrNone(query, [schemaName, indexName]);
2638
+ if (!result) {
2639
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
2640
+ }
2641
+ let columns = [];
2642
+ if (typeof result.columns === "string" && result.columns.startsWith("{") && result.columns.endsWith("}")) {
2643
+ const arrayContent = result.columns.slice(1, -1);
2644
+ columns = arrayContent ? arrayContent.split(",") : [];
2645
+ } else if (Array.isArray(result.columns)) {
2646
+ columns = result.columns;
2647
+ }
2648
+ return {
2649
+ name: result.name,
2650
+ table: result.table,
2651
+ columns,
2652
+ unique: result.is_unique || false,
2653
+ size: result.size || "0",
2654
+ definition: result.definition || "",
2655
+ method: result.method || "btree",
2656
+ scans: parseInt(result.scans) || 0,
2657
+ tuples_read: parseInt(result.tuples_read) || 0,
2658
+ tuples_fetched: parseInt(result.tuples_fetched) || 0
2659
+ };
2660
+ } catch (error$1) {
2661
+ throw new error.MastraError(
2662
+ {
2663
+ id: "MASTRA_STORAGE_PG_INDEX_DESCRIBE_FAILED",
2664
+ domain: error.ErrorDomain.STORAGE,
2665
+ category: error.ErrorCategory.THIRD_PARTY,
2666
+ details: {
2667
+ indexName
2668
+ }
2669
+ },
2670
+ error$1
2671
+ );
2672
+ }
2673
+ }
2377
2674
  };
2378
2675
  function transformScoreRow(row) {
2379
2676
  return {
@@ -3077,6 +3374,11 @@ var PostgresStore = class extends storage.MastraStorage {
3077
3374
  memory
3078
3375
  };
3079
3376
  await super.init();
3377
+ try {
3378
+ await operations.createAutomaticIndexes();
3379
+ } catch (indexError) {
3380
+ console.warn("Failed to create indexes:", indexError);
3381
+ }
3080
3382
  } catch (error$1) {
3081
3383
  this.isConnected = false;
3082
3384
  throw new error.MastraError(
@@ -3107,7 +3409,9 @@ var PostgresStore = class extends storage.MastraStorage {
3107
3409
  resourceWorkingMemory: true,
3108
3410
  hasColumn: true,
3109
3411
  createTable: true,
3110
- deleteMessages: true
3412
+ deleteMessages: true,
3413
+ aiTracing: false,
3414
+ indexManagement: true
3111
3415
  };
3112
3416
  }
3113
3417
  /** @deprecated use getEvals instead */