@mastra/pg 0.16.1 → 0.17.0

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,9 +5,10 @@ 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, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_EVALS, ScoresStorage, TABLE_SCORERS, TracesStorage, safelyParseJSON, WorkflowsStorage, LegacyEvalsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES } from '@mastra/core/storage';
8
+ import { MastraStorage, StoreOperations, TABLE_SCHEMAS, TABLE_WORKFLOW_SNAPSHOT, TABLE_AI_SPANS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_EVALS, TABLE_SCORERS, ScoresStorage, TracesStorage, safelyParseJSON, WorkflowsStorage, LegacyEvalsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES, ObservabilityStorage } from '@mastra/core/storage';
9
9
  import pgPromise from 'pg-promise';
10
10
  import { MessageList } from '@mastra/core/agent';
11
+ import { saveScorePayloadSchema } from '@mastra/core/scores';
11
12
 
12
13
  // src/vector/index.ts
13
14
  var PGFilterTranslator = class extends BaseFilterTranslator {
@@ -1258,6 +1259,68 @@ function getTableName({ indexName, schemaName }) {
1258
1259
  const quotedSchemaName = schemaName;
1259
1260
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1260
1261
  }
1262
+ function buildDateRangeFilter(dateRange, fieldName) {
1263
+ const filters = {};
1264
+ if (dateRange?.start) {
1265
+ filters[`${fieldName}_gte`] = dateRange.start;
1266
+ }
1267
+ if (dateRange?.end) {
1268
+ filters[`${fieldName}_lte`] = dateRange.end;
1269
+ }
1270
+ return filters;
1271
+ }
1272
+ function prepareWhereClause(filters, _schema) {
1273
+ const conditions = [];
1274
+ const args = [];
1275
+ let paramIndex = 1;
1276
+ Object.entries(filters).forEach(([key, value]) => {
1277
+ if (value === void 0) return;
1278
+ if (key.endsWith("_gte")) {
1279
+ const fieldName = key.slice(0, -4);
1280
+ conditions.push(`"${parseSqlIdentifier(fieldName, "field name")}" >= $${paramIndex++}`);
1281
+ args.push(value instanceof Date ? value.toISOString() : value);
1282
+ } else if (key.endsWith("_lte")) {
1283
+ const fieldName = key.slice(0, -4);
1284
+ conditions.push(`"${parseSqlIdentifier(fieldName, "field name")}" <= $${paramIndex++}`);
1285
+ args.push(value instanceof Date ? value.toISOString() : value);
1286
+ } else if (value === null) {
1287
+ conditions.push(`"${parseSqlIdentifier(key, "field name")}" IS NULL`);
1288
+ } else {
1289
+ conditions.push(`"${parseSqlIdentifier(key, "field name")}" = $${paramIndex++}`);
1290
+ args.push(value instanceof Date ? value.toISOString() : value);
1291
+ }
1292
+ });
1293
+ return {
1294
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1295
+ args
1296
+ };
1297
+ }
1298
+ function transformFromSqlRow({
1299
+ tableName,
1300
+ sqlRow
1301
+ }) {
1302
+ const schema = TABLE_SCHEMAS[tableName];
1303
+ const result = {};
1304
+ Object.entries(sqlRow).forEach(([key, value]) => {
1305
+ const columnSchema = schema?.[key];
1306
+ if (columnSchema?.type === "jsonb" && typeof value === "string") {
1307
+ try {
1308
+ result[key] = JSON.parse(value);
1309
+ } catch {
1310
+ result[key] = value;
1311
+ }
1312
+ } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
1313
+ result[key] = new Date(value);
1314
+ } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
1315
+ result[key] = value;
1316
+ } else if (columnSchema?.type === "boolean") {
1317
+ result[key] = Boolean(value);
1318
+ } else {
1319
+ result[key] = value;
1320
+ }
1321
+ });
1322
+ return result;
1323
+ }
1261
1324
 
1262
1325
  // src/storage/domains/legacy-evals/index.ts
1263
1326
  function transformEvalRow(row) {
@@ -2193,6 +2256,316 @@ var MemoryPG = class extends MemoryStorage {
2193
2256
  return updatedResource;
2194
2257
  }
2195
2258
  };
2259
+ var ObservabilityPG = class extends ObservabilityStorage {
2260
+ client;
2261
+ operations;
2262
+ schema;
2263
+ constructor({
2264
+ client,
2265
+ operations,
2266
+ schema
2267
+ }) {
2268
+ super();
2269
+ this.client = client;
2270
+ this.operations = operations;
2271
+ this.schema = schema;
2272
+ }
2273
+ get aiTracingStrategy() {
2274
+ return {
2275
+ preferred: "batch-with-updates",
2276
+ supported: ["batch-with-updates", "insert-only"]
2277
+ };
2278
+ }
2279
+ async createAISpan(span) {
2280
+ try {
2281
+ const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
2282
+ const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
2283
+ const record = {
2284
+ ...span,
2285
+ startedAt,
2286
+ endedAt,
2287
+ startedAtZ: startedAt,
2288
+ endedAtZ: endedAt
2289
+ // Note: createdAt/updatedAt will be set by database triggers
2290
+ };
2291
+ return this.operations.insert({ tableName: TABLE_AI_SPANS, record });
2292
+ } catch (error) {
2293
+ throw new MastraError(
2294
+ {
2295
+ id: "PG_STORE_CREATE_AI_SPAN_FAILED",
2296
+ domain: ErrorDomain.STORAGE,
2297
+ category: ErrorCategory.USER,
2298
+ details: {
2299
+ spanId: span.spanId,
2300
+ traceId: span.traceId,
2301
+ spanType: span.spanType,
2302
+ spanName: span.name
2303
+ }
2304
+ },
2305
+ error
2306
+ );
2307
+ }
2308
+ }
2309
+ async getAITrace(traceId) {
2310
+ try {
2311
+ const tableName = getTableName({
2312
+ indexName: TABLE_AI_SPANS,
2313
+ schemaName: getSchemaName(this.schema)
2314
+ });
2315
+ const spans = await this.client.manyOrNone(
2316
+ `SELECT
2317
+ "traceId", "spanId", "parentSpanId", "name", "scope", "spanType",
2318
+ "attributes", "metadata", "links", "input", "output", "error", "isEvent",
2319
+ "startedAtZ" as "startedAt", "endedAtZ" as "endedAt",
2320
+ "createdAtZ" as "createdAt", "updatedAtZ" as "updatedAt"
2321
+ FROM ${tableName}
2322
+ WHERE "traceId" = $1
2323
+ ORDER BY "startedAtZ" DESC`,
2324
+ [traceId]
2325
+ );
2326
+ if (!spans || spans.length === 0) {
2327
+ return null;
2328
+ }
2329
+ return {
2330
+ traceId,
2331
+ spans: spans.map(
2332
+ (span) => transformFromSqlRow({
2333
+ tableName: TABLE_AI_SPANS,
2334
+ sqlRow: span
2335
+ })
2336
+ )
2337
+ };
2338
+ } catch (error) {
2339
+ throw new MastraError(
2340
+ {
2341
+ id: "PG_STORE_GET_AI_TRACE_FAILED",
2342
+ domain: ErrorDomain.STORAGE,
2343
+ category: ErrorCategory.USER,
2344
+ details: {
2345
+ traceId
2346
+ }
2347
+ },
2348
+ error
2349
+ );
2350
+ }
2351
+ }
2352
+ async updateAISpan({
2353
+ spanId,
2354
+ traceId,
2355
+ updates
2356
+ }) {
2357
+ try {
2358
+ const data = { ...updates };
2359
+ if (data.endedAt instanceof Date) {
2360
+ data.endedAt = data.endedAt.toISOString();
2361
+ }
2362
+ if (data.startedAt instanceof Date) {
2363
+ data.startedAt = data.startedAt.toISOString();
2364
+ }
2365
+ await this.operations.update({
2366
+ tableName: TABLE_AI_SPANS,
2367
+ keys: { spanId, traceId },
2368
+ data
2369
+ });
2370
+ } catch (error) {
2371
+ throw new MastraError(
2372
+ {
2373
+ id: "PG_STORE_UPDATE_AI_SPAN_FAILED",
2374
+ domain: ErrorDomain.STORAGE,
2375
+ category: ErrorCategory.USER,
2376
+ details: {
2377
+ spanId,
2378
+ traceId
2379
+ }
2380
+ },
2381
+ error
2382
+ );
2383
+ }
2384
+ }
2385
+ async getAITracesPaginated({
2386
+ filters,
2387
+ pagination
2388
+ }) {
2389
+ const page = pagination?.page ?? 0;
2390
+ const perPage = pagination?.perPage ?? 10;
2391
+ const { entityId, entityType, ...actualFilters } = filters || {};
2392
+ const filtersWithDateRange = {
2393
+ ...actualFilters,
2394
+ ...buildDateRangeFilter(pagination?.dateRange, "startedAtZ"),
2395
+ parentSpanId: null
2396
+ // Only get root spans for traces
2397
+ };
2398
+ const whereClause = prepareWhereClause(filtersWithDateRange);
2399
+ let actualWhereClause = whereClause.sql;
2400
+ let currentParamIndex = whereClause.args.length + 1;
2401
+ if (entityId && entityType) {
2402
+ let name = "";
2403
+ if (entityType === "workflow") {
2404
+ name = `workflow run: '${entityId}'`;
2405
+ } else if (entityType === "agent") {
2406
+ name = `agent run: '${entityId}'`;
2407
+ } else {
2408
+ const error = new MastraError({
2409
+ id: "PG_STORE_GET_AI_TRACES_PAGINATED_FAILED",
2410
+ domain: ErrorDomain.STORAGE,
2411
+ category: ErrorCategory.USER,
2412
+ details: {
2413
+ entityType
2414
+ },
2415
+ text: `Cannot filter by entity type: ${entityType}`
2416
+ });
2417
+ this.logger?.trackException(error);
2418
+ throw error;
2419
+ }
2420
+ whereClause.args.push(name);
2421
+ const statement = `"name" = $${currentParamIndex++}`;
2422
+ if (actualWhereClause) {
2423
+ actualWhereClause += ` AND ${statement}`;
2424
+ } else {
2425
+ actualWhereClause = ` WHERE ${statement}`;
2426
+ }
2427
+ }
2428
+ const tableName = getTableName({
2429
+ indexName: TABLE_AI_SPANS,
2430
+ schemaName: getSchemaName(this.schema)
2431
+ });
2432
+ try {
2433
+ const countResult = await this.client.oneOrNone(
2434
+ `SELECT COUNT(*) FROM ${tableName}${actualWhereClause}`,
2435
+ whereClause.args
2436
+ );
2437
+ const count = Number(countResult?.count ?? 0);
2438
+ if (count === 0) {
2439
+ return {
2440
+ pagination: {
2441
+ total: 0,
2442
+ page,
2443
+ perPage,
2444
+ hasMore: false
2445
+ },
2446
+ spans: []
2447
+ };
2448
+ }
2449
+ const spans = await this.client.manyOrNone(
2450
+ `SELECT
2451
+ "traceId", "spanId", "parentSpanId", "name", "scope", "spanType",
2452
+ "attributes", "metadata", "links", "input", "output", "error", "isEvent",
2453
+ "startedAtZ" as "startedAt", "endedAtZ" as "endedAt",
2454
+ "createdAtZ" as "createdAt", "updatedAtZ" as "updatedAt"
2455
+ FROM ${tableName}${actualWhereClause}
2456
+ ORDER BY "startedAtZ" DESC
2457
+ LIMIT $${currentParamIndex} OFFSET $${currentParamIndex + 1}`,
2458
+ [...whereClause.args, perPage, page * perPage]
2459
+ );
2460
+ return {
2461
+ pagination: {
2462
+ total: count,
2463
+ page,
2464
+ perPage,
2465
+ hasMore: spans.length === perPage
2466
+ },
2467
+ spans: spans.map(
2468
+ (span) => transformFromSqlRow({
2469
+ tableName: TABLE_AI_SPANS,
2470
+ sqlRow: span
2471
+ })
2472
+ )
2473
+ };
2474
+ } catch (error) {
2475
+ throw new MastraError(
2476
+ {
2477
+ id: "PG_STORE_GET_AI_TRACES_PAGINATED_FAILED",
2478
+ domain: ErrorDomain.STORAGE,
2479
+ category: ErrorCategory.USER
2480
+ },
2481
+ error
2482
+ );
2483
+ }
2484
+ }
2485
+ async batchCreateAISpans(args) {
2486
+ try {
2487
+ const records = args.records.map((record) => {
2488
+ const startedAt = record.startedAt instanceof Date ? record.startedAt.toISOString() : record.startedAt;
2489
+ const endedAt = record.endedAt instanceof Date ? record.endedAt.toISOString() : record.endedAt;
2490
+ return {
2491
+ ...record,
2492
+ startedAt,
2493
+ endedAt,
2494
+ startedAtZ: startedAt,
2495
+ endedAtZ: endedAt
2496
+ // Note: createdAt/updatedAt will be set by database triggers
2497
+ };
2498
+ });
2499
+ return this.operations.batchInsert({
2500
+ tableName: TABLE_AI_SPANS,
2501
+ records
2502
+ });
2503
+ } catch (error) {
2504
+ throw new MastraError(
2505
+ {
2506
+ id: "PG_STORE_BATCH_CREATE_AI_SPANS_FAILED",
2507
+ domain: ErrorDomain.STORAGE,
2508
+ category: ErrorCategory.USER
2509
+ },
2510
+ error
2511
+ );
2512
+ }
2513
+ }
2514
+ async batchUpdateAISpans(args) {
2515
+ try {
2516
+ return this.operations.batchUpdate({
2517
+ tableName: TABLE_AI_SPANS,
2518
+ updates: args.records.map((record) => {
2519
+ const data = {
2520
+ ...record.updates
2521
+ };
2522
+ if (data.endedAt instanceof Date) {
2523
+ const endedAt = data.endedAt.toISOString();
2524
+ data.endedAt = endedAt;
2525
+ data.endedAtZ = endedAt;
2526
+ }
2527
+ if (data.startedAt instanceof Date) {
2528
+ const startedAt = data.startedAt.toISOString();
2529
+ data.startedAt = startedAt;
2530
+ data.startedAtZ = startedAt;
2531
+ }
2532
+ return {
2533
+ keys: { spanId: record.spanId, traceId: record.traceId },
2534
+ data
2535
+ };
2536
+ })
2537
+ });
2538
+ } catch (error) {
2539
+ throw new MastraError(
2540
+ {
2541
+ id: "PG_STORE_BATCH_UPDATE_AI_SPANS_FAILED",
2542
+ domain: ErrorDomain.STORAGE,
2543
+ category: ErrorCategory.USER
2544
+ },
2545
+ error
2546
+ );
2547
+ }
2548
+ }
2549
+ async batchDeleteAITraces(args) {
2550
+ try {
2551
+ const tableName = getTableName({
2552
+ indexName: TABLE_AI_SPANS,
2553
+ schemaName: getSchemaName(this.schema)
2554
+ });
2555
+ const placeholders = args.traceIds.map((_, i) => `$${i + 1}`).join(", ");
2556
+ await this.client.none(`DELETE FROM ${tableName} WHERE "traceId" IN (${placeholders})`, args.traceIds);
2557
+ } catch (error) {
2558
+ throw new MastraError(
2559
+ {
2560
+ id: "PG_STORE_BATCH_DELETE_AI_TRACES_FAILED",
2561
+ domain: ErrorDomain.STORAGE,
2562
+ category: ErrorCategory.USER
2563
+ },
2564
+ error
2565
+ );
2566
+ }
2567
+ }
2568
+ };
2196
2569
  var StoreOperationsPG = class extends StoreOperations {
2197
2570
  client;
2198
2571
  schemaName;
@@ -2211,6 +2584,45 @@ var StoreOperationsPG = class extends StoreOperations {
2211
2584
  );
2212
2585
  return !!result;
2213
2586
  }
2587
+ /**
2588
+ * Prepares values for insertion, handling JSONB columns by stringifying them
2589
+ */
2590
+ prepareValuesForInsert(record, tableName) {
2591
+ return Object.entries(record).map(([key, value]) => {
2592
+ const schema = TABLE_SCHEMAS[tableName];
2593
+ const columnSchema = schema?.[key];
2594
+ if (columnSchema?.type === "jsonb" && value !== null && typeof value === "object") {
2595
+ return JSON.stringify(value);
2596
+ }
2597
+ return value;
2598
+ });
2599
+ }
2600
+ /**
2601
+ * Adds timestamp Z columns to a record if timestamp columns exist
2602
+ */
2603
+ addTimestampZColumns(record) {
2604
+ if (record.createdAt) {
2605
+ record.createdAtZ = record.createdAt;
2606
+ }
2607
+ if (record.created_at) {
2608
+ record.created_atZ = record.created_at;
2609
+ }
2610
+ if (record.updatedAt) {
2611
+ record.updatedAtZ = record.updatedAt;
2612
+ }
2613
+ }
2614
+ /**
2615
+ * Prepares a value for database operations, handling Date objects and JSON serialization
2616
+ */
2617
+ prepareValue(value) {
2618
+ if (value instanceof Date) {
2619
+ return value.toISOString();
2620
+ } else if (typeof value === "object" && value !== null) {
2621
+ return JSON.stringify(value);
2622
+ } else {
2623
+ return value;
2624
+ }
2625
+ }
2214
2626
  async setupSchema() {
2215
2627
  if (!this.schemaName || this.schemaSetupComplete) {
2216
2628
  return;
@@ -2254,18 +2666,10 @@ var StoreOperationsPG = class extends StoreOperations {
2254
2666
  }
2255
2667
  async insert({ tableName, record }) {
2256
2668
  try {
2257
- if (record.createdAt) {
2258
- record.createdAtZ = record.createdAt;
2259
- }
2260
- if (record.created_at) {
2261
- record.created_atZ = record.created_at;
2262
- }
2263
- if (record.updatedAt) {
2264
- record.updatedAtZ = record.updatedAt;
2265
- }
2669
+ this.addTimestampZColumns(record);
2266
2670
  const schemaName = getSchemaName(this.schemaName);
2267
2671
  const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
2268
- const values = Object.values(record);
2672
+ const values = this.prepareValuesForInsert(record, tableName);
2269
2673
  const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
2270
2674
  await this.client.none(
2271
2675
  `INSERT INTO ${getTableName({ indexName: tableName, schemaName })} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
@@ -2360,6 +2764,9 @@ var StoreOperationsPG = class extends StoreOperations {
2360
2764
  schema,
2361
2765
  ifNotExists: timeZColumnNames
2362
2766
  });
2767
+ if (tableName === TABLE_AI_SPANS) {
2768
+ await this.setupTimestampTriggers(tableName);
2769
+ }
2363
2770
  } catch (error) {
2364
2771
  throw new MastraError(
2365
2772
  {
@@ -2374,6 +2781,48 @@ var StoreOperationsPG = class extends StoreOperations {
2374
2781
  );
2375
2782
  }
2376
2783
  }
2784
+ /**
2785
+ * Set up timestamp triggers for a table to automatically manage createdAt/updatedAt
2786
+ */
2787
+ async setupTimestampTriggers(tableName) {
2788
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
2789
+ try {
2790
+ const triggerSQL = `
2791
+ -- Create or replace the trigger function
2792
+ CREATE OR REPLACE FUNCTION trigger_set_timestamps()
2793
+ RETURNS TRIGGER AS $$
2794
+ BEGIN
2795
+ IF TG_OP = 'INSERT' THEN
2796
+ NEW."createdAt" = NOW();
2797
+ NEW."updatedAt" = NOW();
2798
+ NEW."createdAtZ" = NOW();
2799
+ NEW."updatedAtZ" = NOW();
2800
+ ELSIF TG_OP = 'UPDATE' THEN
2801
+ NEW."updatedAt" = NOW();
2802
+ NEW."updatedAtZ" = NOW();
2803
+ -- Prevent createdAt from being changed
2804
+ NEW."createdAt" = OLD."createdAt";
2805
+ NEW."createdAtZ" = OLD."createdAtZ";
2806
+ END IF;
2807
+ RETURN NEW;
2808
+ END;
2809
+ $$ LANGUAGE plpgsql;
2810
+
2811
+ -- Drop existing trigger if it exists
2812
+ DROP TRIGGER IF EXISTS ${tableName}_timestamps ON ${fullTableName};
2813
+
2814
+ -- Create the trigger
2815
+ CREATE TRIGGER ${tableName}_timestamps
2816
+ BEFORE INSERT OR UPDATE ON ${fullTableName}
2817
+ FOR EACH ROW
2818
+ EXECUTE FUNCTION trigger_set_timestamps();
2819
+ `;
2820
+ await this.client.none(triggerSQL);
2821
+ this.logger?.debug?.(`Set up timestamp triggers for table ${fullTableName}`);
2822
+ } catch (error) {
2823
+ this.logger?.warn?.(`Failed to set up timestamp triggers for ${fullTableName}:`, error);
2824
+ }
2825
+ }
2377
2826
  /**
2378
2827
  * Alters table schema to add columns if they don't exist
2379
2828
  * @param tableName Name of the table
@@ -2516,8 +2965,8 @@ var StoreOperationsPG = class extends StoreOperations {
2516
2965
  schemaName: getSchemaName(this.schemaName)
2517
2966
  });
2518
2967
  const indexExists = await this.client.oneOrNone(
2519
- `SELECT 1 FROM pg_indexes
2520
- WHERE indexname = $1
2968
+ `SELECT 1 FROM pg_indexes
2969
+ WHERE indexname = $1
2521
2970
  AND schemaname = $2`,
2522
2971
  [name, schemaName]
2523
2972
  );
@@ -2574,8 +3023,8 @@ var StoreOperationsPG = class extends StoreOperations {
2574
3023
  try {
2575
3024
  const schemaName = this.schemaName || "public";
2576
3025
  const indexExists = await this.client.oneOrNone(
2577
- `SELECT 1 FROM pg_indexes
2578
- WHERE indexname = $1
3026
+ `SELECT 1 FROM pg_indexes
3027
+ WHERE indexname = $1
2579
3028
  AND schemaname = $2`,
2580
3029
  [indexName, schemaName]
2581
3030
  );
@@ -2608,7 +3057,7 @@ var StoreOperationsPG = class extends StoreOperations {
2608
3057
  let params;
2609
3058
  if (tableName) {
2610
3059
  query = `
2611
- SELECT
3060
+ SELECT
2612
3061
  i.indexname as name,
2613
3062
  i.tablename as table,
2614
3063
  i.indexdef as definition,
@@ -2619,14 +3068,14 @@ var StoreOperationsPG = class extends StoreOperations {
2619
3068
  JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
2620
3069
  JOIN pg_index ix ON ix.indexrelid = c.oid
2621
3070
  JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2622
- WHERE i.schemaname = $1
3071
+ WHERE i.schemaname = $1
2623
3072
  AND i.tablename = $2
2624
3073
  GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
2625
3074
  `;
2626
3075
  params = [schemaName, tableName];
2627
3076
  } else {
2628
3077
  query = `
2629
- SELECT
3078
+ SELECT
2630
3079
  i.indexname as name,
2631
3080
  i.tablename as table,
2632
3081
  i.indexdef as definition,
@@ -2705,6 +3154,33 @@ var StoreOperationsPG = class extends StoreOperations {
2705
3154
  name: `${schemaPrefix}mastra_evals_agent_name_created_at_idx`,
2706
3155
  table: TABLE_EVALS,
2707
3156
  columns: ["agent_name", "created_at DESC"]
3157
+ },
3158
+ // Composite index for scores (filter + sort)
3159
+ {
3160
+ name: `${schemaPrefix}mastra_scores_trace_id_span_id_created_at_idx`,
3161
+ table: TABLE_SCORERS,
3162
+ columns: ["trace_id", "span_id", "created_at DESC"]
3163
+ },
3164
+ // AI Spans indexes for optimal trace querying
3165
+ {
3166
+ name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
3167
+ table: TABLE_AI_SPANS,
3168
+ columns: ["traceId", "startedAt DESC"]
3169
+ },
3170
+ {
3171
+ name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
3172
+ table: TABLE_AI_SPANS,
3173
+ columns: ["parentSpanId", "startedAt DESC"]
3174
+ },
3175
+ {
3176
+ name: `${schemaPrefix}mastra_ai_spans_name_idx`,
3177
+ table: TABLE_AI_SPANS,
3178
+ columns: ["name"]
3179
+ },
3180
+ {
3181
+ name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
3182
+ table: TABLE_AI_SPANS,
3183
+ columns: ["spanType", "startedAt DESC"]
2708
3184
  }
2709
3185
  ];
2710
3186
  for (const indexOptions of indexes) {
@@ -2732,7 +3208,7 @@ var StoreOperationsPG = class extends StoreOperations {
2732
3208
  try {
2733
3209
  const schemaName = this.schemaName || "public";
2734
3210
  const query = `
2735
- SELECT
3211
+ SELECT
2736
3212
  i.indexname as name,
2737
3213
  i.tablename as table,
2738
3214
  i.indexdef as definition,
@@ -2749,7 +3225,7 @@ var StoreOperationsPG = class extends StoreOperations {
2749
3225
  JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
2750
3226
  JOIN pg_am am ON c.relam = am.oid
2751
3227
  LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
2752
- WHERE i.schemaname = $1
3228
+ WHERE i.schemaname = $1
2753
3229
  AND i.indexname = $2
2754
3230
  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
2755
3231
  `;
@@ -2790,6 +3266,121 @@ var StoreOperationsPG = class extends StoreOperations {
2790
3266
  );
2791
3267
  }
2792
3268
  }
3269
+ /**
3270
+ * Update a single record in the database
3271
+ */
3272
+ async update({
3273
+ tableName,
3274
+ keys,
3275
+ data
3276
+ }) {
3277
+ try {
3278
+ const setColumns = [];
3279
+ const setValues = [];
3280
+ let paramIndex = 1;
3281
+ Object.entries(data).forEach(([key, value]) => {
3282
+ const parsedKey = parseSqlIdentifier(key, "column name");
3283
+ setColumns.push(`"${parsedKey}" = $${paramIndex++}`);
3284
+ setValues.push(this.prepareValue(value));
3285
+ });
3286
+ const whereConditions = [];
3287
+ const whereValues = [];
3288
+ Object.entries(keys).forEach(([key, value]) => {
3289
+ const parsedKey = parseSqlIdentifier(key, "column name");
3290
+ whereConditions.push(`"${parsedKey}" = $${paramIndex++}`);
3291
+ whereValues.push(this.prepareValue(value));
3292
+ });
3293
+ const tableName_ = getTableName({
3294
+ indexName: tableName,
3295
+ schemaName: getSchemaName(this.schemaName)
3296
+ });
3297
+ const sql = `UPDATE ${tableName_} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
3298
+ const values = [...setValues, ...whereValues];
3299
+ await this.client.none(sql, values);
3300
+ } catch (error) {
3301
+ throw new MastraError(
3302
+ {
3303
+ id: "MASTRA_STORAGE_PG_STORE_UPDATE_FAILED",
3304
+ domain: ErrorDomain.STORAGE,
3305
+ category: ErrorCategory.THIRD_PARTY,
3306
+ details: {
3307
+ tableName
3308
+ }
3309
+ },
3310
+ error
3311
+ );
3312
+ }
3313
+ }
3314
+ /**
3315
+ * Update multiple records in a single batch transaction
3316
+ */
3317
+ async batchUpdate({
3318
+ tableName,
3319
+ updates
3320
+ }) {
3321
+ try {
3322
+ await this.client.query("BEGIN");
3323
+ for (const { keys, data } of updates) {
3324
+ await this.update({ tableName, keys, data });
3325
+ }
3326
+ await this.client.query("COMMIT");
3327
+ } catch (error) {
3328
+ await this.client.query("ROLLBACK");
3329
+ throw new MastraError(
3330
+ {
3331
+ id: "MASTRA_STORAGE_PG_STORE_BATCH_UPDATE_FAILED",
3332
+ domain: ErrorDomain.STORAGE,
3333
+ category: ErrorCategory.THIRD_PARTY,
3334
+ details: {
3335
+ tableName,
3336
+ numberOfRecords: updates.length
3337
+ }
3338
+ },
3339
+ error
3340
+ );
3341
+ }
3342
+ }
3343
+ /**
3344
+ * Delete multiple records by keys
3345
+ */
3346
+ async batchDelete({ tableName, keys }) {
3347
+ try {
3348
+ if (keys.length === 0) {
3349
+ return;
3350
+ }
3351
+ const tableName_ = getTableName({
3352
+ indexName: tableName,
3353
+ schemaName: getSchemaName(this.schemaName)
3354
+ });
3355
+ await this.client.tx(async (t) => {
3356
+ for (const keySet of keys) {
3357
+ const conditions = [];
3358
+ const values = [];
3359
+ let paramIndex = 1;
3360
+ Object.entries(keySet).forEach(([key, value]) => {
3361
+ const parsedKey = parseSqlIdentifier(key, "column name");
3362
+ conditions.push(`"${parsedKey}" = $${paramIndex++}`);
3363
+ values.push(value);
3364
+ });
3365
+ const sql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
3366
+ await t.none(sql, values);
3367
+ }
3368
+ });
3369
+ } catch (error) {
3370
+ throw new MastraError(
3371
+ {
3372
+ id: "MASTRA_STORAGE_PG_STORE_BATCH_DELETE_FAILED",
3373
+ domain: ErrorDomain.STORAGE,
3374
+ category: ErrorCategory.THIRD_PARTY,
3375
+ details: {
3376
+ tableName,
3377
+ numberOfRecords: keys.length
3378
+ }
3379
+ },
3380
+ error
3381
+ );
3382
+ }
3383
+ }
2793
3384
  };
2794
3385
  function transformScoreRow(row) {
2795
3386
  return {
@@ -2903,6 +3494,26 @@ var ScoresPG = class extends ScoresStorage {
2903
3494
  }
2904
3495
  }
2905
3496
  async saveScore(score) {
3497
+ let parsedScore;
3498
+ try {
3499
+ parsedScore = saveScorePayloadSchema.parse(score);
3500
+ } catch (error) {
3501
+ throw new MastraError(
3502
+ {
3503
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
3504
+ domain: ErrorDomain.STORAGE,
3505
+ category: ErrorCategory.USER,
3506
+ details: {
3507
+ scorer: score.scorer.name,
3508
+ entityId: score.entityId,
3509
+ entityType: score.entityType,
3510
+ traceId: score.traceId || "",
3511
+ spanId: score.spanId || ""
3512
+ }
3513
+ },
3514
+ error
3515
+ );
3516
+ }
2906
3517
  try {
2907
3518
  const id = crypto.randomUUID();
2908
3519
  const {
@@ -2916,7 +3527,7 @@ var ScoresPG = class extends ScoresStorage {
2916
3527
  runtimeContext,
2917
3528
  entity,
2918
3529
  ...rest
2919
- } = score;
3530
+ } = parsedScore;
2920
3531
  await this.operations.insert({
2921
3532
  tableName: TABLE_SCORERS,
2922
3533
  record: {
@@ -3037,6 +3648,44 @@ var ScoresPG = class extends ScoresStorage {
3037
3648
  );
3038
3649
  }
3039
3650
  }
3651
+ async getScoresBySpan({
3652
+ traceId,
3653
+ spanId,
3654
+ pagination
3655
+ }) {
3656
+ try {
3657
+ const tableName = getTableName({ indexName: TABLE_SCORERS, schemaName: this.schema });
3658
+ const countSQLResult = await this.client.oneOrNone(
3659
+ `SELECT COUNT(*) as count FROM ${tableName} WHERE "traceId" = $1 AND "spanId" = $2`,
3660
+ [traceId, spanId]
3661
+ );
3662
+ const total = Number(countSQLResult?.count ?? 0);
3663
+ const result = await this.client.manyOrNone(
3664
+ `SELECT * FROM ${tableName} WHERE "traceId" = $1 AND "spanId" = $2 ORDER BY "createdAt" DESC LIMIT $3 OFFSET $4`,
3665
+ [traceId, spanId, pagination.perPage + 1, pagination.page * pagination.perPage]
3666
+ );
3667
+ const hasMore = result.length > pagination.perPage;
3668
+ const scores = result.slice(0, pagination.perPage).map((row) => transformScoreRow(row)) ?? [];
3669
+ return {
3670
+ scores,
3671
+ pagination: {
3672
+ total,
3673
+ page: pagination.page,
3674
+ perPage: pagination.perPage,
3675
+ hasMore
3676
+ }
3677
+ };
3678
+ } catch (error) {
3679
+ throw new MastraError(
3680
+ {
3681
+ id: "MASTRA_STORAGE_PG_STORE_GET_SCORES_BY_SPAN_FAILED",
3682
+ domain: ErrorDomain.STORAGE,
3683
+ category: ErrorCategory.THIRD_PARTY
3684
+ },
3685
+ error
3686
+ );
3687
+ }
3688
+ }
3040
3689
  };
3041
3690
  var TracesPG = class extends TracesStorage {
3042
3691
  client;
@@ -3432,7 +4081,8 @@ var PostgresStore = class extends MastraStorage {
3432
4081
  this.#config = {
3433
4082
  connectionString: config.connectionString,
3434
4083
  max: config.max,
3435
- idleTimeoutMillis: config.idleTimeoutMillis
4084
+ idleTimeoutMillis: config.idleTimeoutMillis,
4085
+ ssl: config.ssl
3436
4086
  };
3437
4087
  } else if (isCloudSqlConfig(config)) {
3438
4088
  this.#config = {
@@ -3452,11 +4102,9 @@ var PostgresStore = class extends MastraStorage {
3452
4102
  idleTimeoutMillis: config.idleTimeoutMillis
3453
4103
  };
3454
4104
  } else {
3455
- this.#config = {
3456
- ...config,
3457
- max: config.max,
3458
- idleTimeoutMillis: config.idleTimeoutMillis
3459
- };
4105
+ throw new Error(
4106
+ "PostgresStore: invalid config. Provide either {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
4107
+ );
3460
4108
  }
3461
4109
  this.stores = {};
3462
4110
  } catch (e) {
@@ -3484,13 +4132,15 @@ var PostgresStore = class extends MastraStorage {
3484
4132
  const workflows = new WorkflowsPG({ client: this.#db, operations, schema: this.schema });
3485
4133
  const legacyEvals = new LegacyEvalsPG({ client: this.#db, schema: this.schema });
3486
4134
  const memory = new MemoryPG({ client: this.#db, schema: this.schema, operations });
4135
+ const observability = new ObservabilityPG({ client: this.#db, operations, schema: this.schema });
3487
4136
  this.stores = {
3488
4137
  operations,
3489
4138
  scores,
3490
4139
  traces,
3491
4140
  workflows,
3492
4141
  legacyEvals,
3493
- memory
4142
+ memory,
4143
+ observability
3494
4144
  };
3495
4145
  await super.init();
3496
4146
  try {
@@ -3529,8 +4179,9 @@ var PostgresStore = class extends MastraStorage {
3529
4179
  hasColumn: true,
3530
4180
  createTable: true,
3531
4181
  deleteMessages: true,
3532
- aiTracing: false,
3533
- indexManagement: true
4182
+ aiTracing: true,
4183
+ indexManagement: true,
4184
+ getScoresBySpan: true
3534
4185
  };
3535
4186
  }
3536
4187
  /** @deprecated use getEvals instead */
@@ -3696,11 +4347,95 @@ var PostgresStore = class extends MastraStorage {
3696
4347
  async close() {
3697
4348
  this.pgp.end();
3698
4349
  }
4350
+ /**
4351
+ * AI Tracing / Observability
4352
+ */
4353
+ async createAISpan(span) {
4354
+ if (!this.stores.observability) {
4355
+ throw new MastraError({
4356
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4357
+ domain: ErrorDomain.STORAGE,
4358
+ category: ErrorCategory.SYSTEM,
4359
+ text: "Observability storage is not initialized"
4360
+ });
4361
+ }
4362
+ return this.stores.observability.createAISpan(span);
4363
+ }
4364
+ async updateAISpan({
4365
+ spanId,
4366
+ traceId,
4367
+ updates
4368
+ }) {
4369
+ if (!this.stores.observability) {
4370
+ throw new MastraError({
4371
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4372
+ domain: ErrorDomain.STORAGE,
4373
+ category: ErrorCategory.SYSTEM,
4374
+ text: "Observability storage is not initialized"
4375
+ });
4376
+ }
4377
+ return this.stores.observability.updateAISpan({ spanId, traceId, updates });
4378
+ }
4379
+ async getAITrace(traceId) {
4380
+ if (!this.stores.observability) {
4381
+ throw new MastraError({
4382
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4383
+ domain: ErrorDomain.STORAGE,
4384
+ category: ErrorCategory.SYSTEM,
4385
+ text: "Observability storage is not initialized"
4386
+ });
4387
+ }
4388
+ return this.stores.observability.getAITrace(traceId);
4389
+ }
4390
+ async getAITracesPaginated(args) {
4391
+ if (!this.stores.observability) {
4392
+ throw new MastraError({
4393
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4394
+ domain: ErrorDomain.STORAGE,
4395
+ category: ErrorCategory.SYSTEM,
4396
+ text: "Observability storage is not initialized"
4397
+ });
4398
+ }
4399
+ return this.stores.observability.getAITracesPaginated(args);
4400
+ }
4401
+ async batchCreateAISpans(args) {
4402
+ if (!this.stores.observability) {
4403
+ throw new MastraError({
4404
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4405
+ domain: ErrorDomain.STORAGE,
4406
+ category: ErrorCategory.SYSTEM,
4407
+ text: "Observability storage is not initialized"
4408
+ });
4409
+ }
4410
+ return this.stores.observability.batchCreateAISpans(args);
4411
+ }
4412
+ async batchUpdateAISpans(args) {
4413
+ if (!this.stores.observability) {
4414
+ throw new MastraError({
4415
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4416
+ domain: ErrorDomain.STORAGE,
4417
+ category: ErrorCategory.SYSTEM,
4418
+ text: "Observability storage is not initialized"
4419
+ });
4420
+ }
4421
+ return this.stores.observability.batchUpdateAISpans(args);
4422
+ }
4423
+ async batchDeleteAITraces(args) {
4424
+ if (!this.stores.observability) {
4425
+ throw new MastraError({
4426
+ id: "PG_STORE_OBSERVABILITY_NOT_INITIALIZED",
4427
+ domain: ErrorDomain.STORAGE,
4428
+ category: ErrorCategory.SYSTEM,
4429
+ text: "Observability storage is not initialized"
4430
+ });
4431
+ }
4432
+ return this.stores.observability.batchDeleteAITraces(args);
4433
+ }
3699
4434
  /**
3700
4435
  * Scorers
3701
4436
  */
3702
- async getScoreById({ id: _id }) {
3703
- return this.stores.scores.getScoreById({ id: _id });
4437
+ async getScoreById({ id }) {
4438
+ return this.stores.scores.getScoreById({ id });
3704
4439
  }
3705
4440
  async getScoresByScorerId({
3706
4441
  scorerId,
@@ -3711,26 +4446,33 @@ var PostgresStore = class extends MastraStorage {
3711
4446
  }) {
3712
4447
  return this.stores.scores.getScoresByScorerId({ scorerId, pagination, entityId, entityType, source });
3713
4448
  }
3714
- async saveScore(_score) {
3715
- return this.stores.scores.saveScore(_score);
4449
+ async saveScore(score) {
4450
+ return this.stores.scores.saveScore(score);
3716
4451
  }
3717
4452
  async getScoresByRunId({
3718
- runId: _runId,
3719
- pagination: _pagination
4453
+ runId,
4454
+ pagination
3720
4455
  }) {
3721
- return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
4456
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
3722
4457
  }
3723
4458
  async getScoresByEntityId({
3724
- entityId: _entityId,
3725
- entityType: _entityType,
3726
- pagination: _pagination
4459
+ entityId,
4460
+ entityType,
4461
+ pagination
3727
4462
  }) {
3728
4463
  return this.stores.scores.getScoresByEntityId({
3729
- entityId: _entityId,
3730
- entityType: _entityType,
3731
- pagination: _pagination
4464
+ entityId,
4465
+ entityType,
4466
+ pagination
3732
4467
  });
3733
4468
  }
4469
+ async getScoresBySpan({
4470
+ traceId,
4471
+ spanId,
4472
+ pagination
4473
+ }) {
4474
+ return this.stores.scores.getScoresBySpan({ traceId, spanId, pagination });
4475
+ }
3734
4476
  };
3735
4477
 
3736
4478
  // src/vector/prompt.ts