@mastra/pg 1.7.2 → 1.8.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/docs/SKILL.md +14 -14
  3. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  4. package/dist/docs/references/docs-memory-semantic-recall.md +9 -9
  5. package/dist/docs/references/docs-memory-storage.md +1 -1
  6. package/dist/docs/references/docs-memory-working-memory.md +20 -20
  7. package/dist/docs/references/docs-rag-overview.md +2 -2
  8. package/dist/docs/references/docs-rag-retrieval.md +4 -4
  9. package/dist/docs/references/docs-rag-vector-databases.md +13 -13
  10. package/dist/docs/references/reference-memory-memory-class.md +1 -1
  11. package/dist/docs/references/reference-processors-message-history-processor.md +1 -1
  12. package/dist/docs/references/reference-rag-metadata-filters.md +15 -15
  13. package/dist/docs/references/reference-storage-composite.md +1 -1
  14. package/dist/docs/references/reference-storage-dynamodb.md +7 -7
  15. package/dist/docs/references/reference-storage-postgresql.md +7 -7
  16. package/dist/docs/references/reference-tools-vector-query-tool.md +12 -12
  17. package/dist/docs/references/reference-vectors-pg.md +23 -21
  18. package/dist/index.cjs +379 -91
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +379 -91
  21. package/dist/index.js.map +1 -1
  22. package/dist/storage/db/index.d.ts +13 -0
  23. package/dist/storage/db/index.d.ts.map +1 -1
  24. package/dist/storage/domains/datasets/index.d.ts.map +1 -1
  25. package/dist/storage/domains/memory/index.d.ts +7 -2
  26. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  27. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  28. package/dist/vector/index.d.ts +44 -10
  29. package/dist/vector/index.d.ts.map +1 -1
  30. package/dist/vector/types.d.ts +32 -2
  31. package/dist/vector/types.d.ts.map +1 -1
  32. package/package.json +7 -7
package/dist/index.cjs CHANGED
@@ -748,11 +748,22 @@ var PgVector = class extends vector.MastraVector {
748
748
  }
749
749
  return major > 0 || major === 0 && minor >= 7;
750
750
  }
751
+ /** Checks if pgvector >= 0.7.0 (required for bit type). */
752
+ supportsBit() {
753
+ return this.supportsHalfvec();
754
+ }
755
+ /** Checks if pgvector >= 0.7.0 (required for sparsevec type). */
756
+ supportsSparsevec() {
757
+ return this.supportsHalfvec();
758
+ }
751
759
  /**
752
760
  * Gets the properly qualified vector type name
753
- * @param vectorType - The type of vector storage ('vector' or 'halfvec')
761
+ * @param vectorType - The type of vector storage
754
762
  */
755
- getVectorTypeName(vectorType = "vector") {
763
+ getVectorTypeName(vectorType = "vector", dimension) {
764
+ if (vectorType === "bit") {
765
+ return dimension ? `bit(${dimension})` : "bit";
766
+ }
756
767
  if (this.vectorExtensionSchema) {
757
768
  if (this.vectorExtensionSchema === "pg_catalog") {
758
769
  return vectorType;
@@ -763,20 +774,76 @@ var PgVector = class extends vector.MastraVector {
763
774
  return vectorType;
764
775
  }
765
776
  /**
766
- * Gets the operator class for index creation based on metric and vector type.
767
- * pgvector uses different operator classes for vector vs halfvec types.
777
+ * Returns the operator class, distance operator, and score expression for a
778
+ * standard (non-bit) vector type prefix and metric.
768
779
  */
769
- getMetricOperatorClass(metric, vectorType) {
770
- const prefix = vectorType === "halfvec" ? "halfvec" : "vector";
780
+ getMetricOps(prefix, metric) {
771
781
  switch (metric) {
772
- case "cosine":
773
- return `${prefix}_cosine_ops`;
774
782
  case "euclidean":
775
- return `${prefix}_l2_ops`;
783
+ return {
784
+ operatorClass: `${prefix}_l2_ops`,
785
+ distanceOperator: "<->",
786
+ scoreExpr: (d) => `1.0 / (1.0 + (${d}))`
787
+ };
776
788
  case "dotproduct":
777
- return `${prefix}_ip_ops`;
789
+ return {
790
+ operatorClass: `${prefix}_ip_ops`,
791
+ distanceOperator: "<#>",
792
+ scoreExpr: (d) => `(${d}) * -1`
793
+ };
794
+ default:
795
+ return {
796
+ operatorClass: `${prefix}_cosine_ops`,
797
+ distanceOperator: "<=>",
798
+ scoreExpr: (d) => `1 - (${d})`
799
+ };
800
+ }
801
+ }
802
+ /**
803
+ * Returns all vector-type-specific operations for the given vectorType and metric.
804
+ */
805
+ getVectorOps(vectorType, metric) {
806
+ switch (vectorType) {
807
+ case "bit":
808
+ return {
809
+ operatorClass: metric === "jaccard" ? "bit_jaccard_ops" : "bit_hamming_ops",
810
+ distanceOperator: metric === "jaccard" ? "<%>" : "<~>",
811
+ scoreExpr: (d) => metric === "jaccard" ? `1 - (${d})` : `1 - ((${d})::float / bit_length(embedding))`,
812
+ formatVector: (v) => v.map((x) => x ? "1" : "0").join(""),
813
+ parseEmbedding: (e) => e.split("").map((c) => c === "1" ? 1 : 0)
814
+ };
815
+ case "sparsevec":
816
+ return {
817
+ ...this.getMetricOps("sparsevec", metric),
818
+ formatVector: (v, dimension) => {
819
+ const dim = dimension ?? v.length;
820
+ const nonZero = v.map((val, i) => val !== 0 ? `${i + 1}:${val}` : null).filter(Boolean).join(",");
821
+ return `{${nonZero}}/${dim}`;
822
+ },
823
+ parseEmbedding: (e) => {
824
+ const match = e.match(/^\{([^}]*)\}\/(\d+)$/);
825
+ if (!match) return [];
826
+ const dim = parseInt(match[2], 10);
827
+ const result = new Array(dim).fill(0);
828
+ const entries = match[1];
829
+ if (entries.trim()) {
830
+ for (const entry of entries.split(",")) {
831
+ const [idxStr, valStr] = entry.split(":");
832
+ const idx = parseInt(idxStr.trim(), 10) - 1;
833
+ result[idx] = parseFloat(valStr.trim());
834
+ }
835
+ }
836
+ return result;
837
+ }
838
+ };
839
+ case "halfvec":
840
+ case "vector":
778
841
  default:
779
- return `${prefix}_cosine_ops`;
842
+ return {
843
+ ...this.getMetricOps(vectorType === "halfvec" ? "halfvec" : "vector", metric),
844
+ formatVector: (v) => `[${v.join(",")}]`,
845
+ parseEmbedding: (e) => JSON.parse(e)
846
+ };
780
847
  }
781
848
  }
782
849
  getTableName(indexName) {
@@ -879,10 +946,12 @@ var PgVector = class extends vector.MastraVector {
879
946
  const client = await this.pool.connect();
880
947
  try {
881
948
  await client.query("BEGIN");
882
- const vectorStr = `[${queryVector.join(",")}]`;
883
949
  const translatedFilter = this.transformFilter(filter);
884
950
  const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
885
951
  const indexInfo = await this.getIndexInfo({ indexName });
952
+ const metric = indexInfo.metric ?? "cosine";
953
+ const ops = this.getVectorOps(indexInfo.vectorType, metric);
954
+ const vectorStr = ops.formatVector(queryVector, indexInfo.dimension);
886
955
  if (indexInfo.type === "hnsw") {
887
956
  const calculatedEf = ef ?? Math.max(topK, (indexInfo?.config?.m ?? 16) * topK);
888
957
  const searchEf = Math.min(1e3, Math.max(1, calculatedEf));
@@ -892,12 +961,14 @@ var PgVector = class extends vector.MastraVector {
892
961
  await client.query(`SET LOCAL ivfflat.probes = ${probes}`);
893
962
  }
894
963
  const { tableName } = this.getTableName(indexName);
895
- const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
964
+ const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
965
+ const distanceExpr = `embedding ${ops.distanceOperator} '${vectorStr}'::${qualifiedVectorType}`;
966
+ const scoreExpr = ops.scoreExpr(distanceExpr);
896
967
  const query = `
897
968
  WITH vector_scores AS (
898
969
  SELECT
899
970
  vector_id as id,
900
- 1 - (embedding <=> '${vectorStr}'::${qualifiedVectorType}) as score,
971
+ ${scoreExpr} as score,
901
972
  metadata
902
973
  ${includeVector ? ", embedding" : ""}
903
974
  FROM ${tableName}
@@ -914,7 +985,7 @@ var PgVector = class extends vector.MastraVector {
914
985
  id,
915
986
  score,
916
987
  metadata,
917
- ...includeVector && embedding && { vector: JSON.parse(embedding) }
988
+ ...includeVector && embedding && { vector: ops.parseEmbedding(embedding) }
918
989
  }));
919
990
  } catch (error$1) {
920
991
  await client.query("ROLLBACK");
@@ -963,8 +1034,10 @@ var PgVector = class extends vector.MastraVector {
963
1034
  }
964
1035
  const vectorIds = ids || vectors.map(() => crypto.randomUUID());
965
1036
  const indexInfo = await this.getIndexInfo({ indexName });
966
- const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
1037
+ const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
1038
+ const ops = this.getVectorOps(indexInfo.vectorType, indexInfo.metric ?? "cosine");
967
1039
  for (let i = 0; i < vectors.length; i++) {
1040
+ const vectorStr = ops.formatVector(vectors[i], indexInfo.dimension);
968
1041
  const query = `
969
1042
  INSERT INTO ${tableName} (vector_id, embedding, metadata)
970
1043
  VALUES ($1, $2::${qualifiedVectorType}, $3::jsonb)
@@ -974,7 +1047,7 @@ var PgVector = class extends vector.MastraVector {
974
1047
  metadata = $3::jsonb
975
1048
  RETURNING embedding::text
976
1049
  `;
977
- await client.query(query, [vectorIds[i], `[${vectors[i]?.join(",")}]`, JSON.stringify(metadata?.[i] || {})]);
1050
+ await client.query(query, [vectorIds[i], vectorStr, JSON.stringify(metadata?.[i] || {})]);
978
1051
  }
979
1052
  await client.query("COMMIT");
980
1053
  this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
@@ -1030,9 +1103,17 @@ var PgVector = class extends vector.MastraVector {
1030
1103
  dimension,
1031
1104
  metric,
1032
1105
  type,
1033
- vectorType = "vector"
1106
+ vectorType = "vector",
1107
+ metadataIndexes
1034
1108
  }) {
1035
- const input = indexName + dimension + metric + (type || "ivfflat") + vectorType;
1109
+ const input = JSON.stringify([
1110
+ indexName,
1111
+ dimension,
1112
+ metric,
1113
+ type || "ivfflat",
1114
+ vectorType,
1115
+ metadataIndexes?.toSorted() ?? []
1116
+ ]);
1036
1117
  return (await this.hasher).h32(input);
1037
1118
  }
1038
1119
  cachedIndexExists(indexName, newKey) {
@@ -1083,11 +1164,13 @@ var PgVector = class extends vector.MastraVector {
1083
1164
  async createIndex({
1084
1165
  indexName,
1085
1166
  dimension,
1086
- metric = "cosine",
1167
+ metric: rawMetric = "cosine",
1087
1168
  indexConfig = {},
1088
1169
  buildIndex = true,
1089
- vectorType = "vector"
1170
+ vectorType = "vector",
1171
+ metadataIndexes
1090
1172
  }) {
1173
+ const metric = vectorType === "bit" && rawMetric !== "hamming" && rawMetric !== "jaccard" ? "hamming" : rawMetric;
1091
1174
  const { tableName } = this.getTableName(indexName);
1092
1175
  try {
1093
1176
  if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
@@ -1096,8 +1179,17 @@ var PgVector = class extends vector.MastraVector {
1096
1179
  if (!Number.isInteger(dimension) || dimension <= 0) {
1097
1180
  throw new Error("Dimension must be a positive integer");
1098
1181
  }
1099
- if (vectorType !== "vector" && vectorType !== "halfvec") {
1100
- throw new Error('vectorType must be "vector" or "halfvec"');
1182
+ if (vectorType !== "vector" && vectorType !== "halfvec" && vectorType !== "bit" && vectorType !== "sparsevec") {
1183
+ throw new Error('vectorType must be "vector", "halfvec", "bit", or "sparsevec"');
1184
+ }
1185
+ if (vectorType === "bit" && dimension > 64e3) {
1186
+ throw new Error("bit vectors support up to 64,000 dimensions for indexes");
1187
+ }
1188
+ if ((metric === "hamming" || metric === "jaccard") && vectorType !== "bit") {
1189
+ throw new Error(`${metric} metric is only valid with vectorType 'bit'`);
1190
+ }
1191
+ if (indexConfig?.type === "ivfflat" && vectorType === "bit" && metric === "jaccard") {
1192
+ throw new Error("IVFFlat indexes do not support Jaccard distance for bit vectors. Use HNSW instead.");
1101
1193
  }
1102
1194
  } catch (error$1) {
1103
1195
  const mastraError = new error.MastraError(
@@ -1119,7 +1211,8 @@ var PgVector = class extends vector.MastraVector {
1119
1211
  dimension,
1120
1212
  type: indexConfig.type,
1121
1213
  metric,
1122
- vectorType
1214
+ vectorType,
1215
+ metadataIndexes
1123
1216
  });
1124
1217
  if (this.cachedIndexExists(indexName, indexCacheKey)) {
1125
1218
  return;
@@ -1147,6 +1240,33 @@ var PgVector = class extends vector.MastraVector {
1147
1240
  }
1148
1241
  });
1149
1242
  }
1243
+ if (vectorType === "bit" && !this.supportsBit()) {
1244
+ throw new error.MastraError({
1245
+ id: storage.createVectorErrorId("PG", "CREATE_INDEX", "VECTOR_TYPE_NOT_SUPPORTED"),
1246
+ text: `${vectorType} type requires pgvector >= 0.7.0, but version ${this.vectorExtensionVersion || "unknown"} is installed. Either upgrade pgvector or use vectorType: 'vector'.`,
1247
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1248
+ category: error.ErrorCategory.USER,
1249
+ details: {
1250
+ indexName,
1251
+ vectorType,
1252
+ installedVersion: this.vectorExtensionVersion
1253
+ }
1254
+ });
1255
+ }
1256
+ if (vectorType === "sparsevec" && !this.supportsSparsevec()) {
1257
+ throw new error.MastraError({
1258
+ id: storage.createVectorErrorId("PG", "CREATE_INDEX", "VECTOR_TYPE_NOT_SUPPORTED"),
1259
+ text: `${vectorType} type requires pgvector >= 0.7.0, but version ${this.vectorExtensionVersion || "unknown"} is installed. Either upgrade pgvector or use vectorType: 'vector'.`,
1260
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1261
+ category: error.ErrorCategory.USER,
1262
+ details: {
1263
+ indexName,
1264
+ requestedVectorType: vectorType,
1265
+ pgvectorVersion: this.vectorExtensionVersion || "unknown",
1266
+ requiredVersion: "0.7.0"
1267
+ }
1268
+ });
1269
+ }
1150
1270
  if (this.schema && this.vectorExtensionSchema && this.schema !== this.vectorExtensionSchema && this.vectorExtensionSchema !== "pg_catalog") {
1151
1271
  await client.query(`SET search_path TO ${this.getSchemaName()}, "${this.vectorExtensionSchema}"`);
1152
1272
  }
@@ -1164,6 +1284,9 @@ var PgVector = class extends vector.MastraVector {
1164
1284
  if (buildIndex) {
1165
1285
  await this.setupIndex({ indexName, metric, indexConfig, vectorType }, client);
1166
1286
  }
1287
+ if (metadataIndexes?.length) {
1288
+ await this.createMetadataIndexes(tableName, indexName, metadataIndexes);
1289
+ }
1167
1290
  } catch (error) {
1168
1291
  this.createdIndexes.delete(indexName);
1169
1292
  this.indexVectorTypes.delete(indexName);
@@ -1187,10 +1310,10 @@ var PgVector = class extends vector.MastraVector {
1187
1310
  throw mastraError;
1188
1311
  });
1189
1312
  }
1190
- async buildIndex({ indexName, metric = "cosine", indexConfig }) {
1313
+ async buildIndex({ indexName, metric = "cosine", indexConfig, vectorType }) {
1191
1314
  const client = await this.pool.connect();
1192
1315
  try {
1193
- await this.setupIndex({ indexName, metric, indexConfig }, client);
1316
+ await this.setupIndex({ indexName, metric, indexConfig, vectorType }, client);
1194
1317
  } catch (error$1) {
1195
1318
  const mastraError = new error.MastraError(
1196
1319
  {
@@ -1213,7 +1336,26 @@ var PgVector = class extends vector.MastraVector {
1213
1336
  const mutex = this.getMutexByName(`build-${indexName}`);
1214
1337
  await mutex.runExclusive(async () => {
1215
1338
  const isConfigEmpty = !indexConfig || Object.keys(indexConfig).length === 0 || !indexConfig.type && !indexConfig.ivf && !indexConfig.hnsw;
1216
- const indexType = isConfigEmpty ? "ivfflat" : indexConfig.type || "ivfflat";
1339
+ const defaultIndexType = vectorType === "sparsevec" ? "hnsw" : "ivfflat";
1340
+ const indexType = isConfigEmpty ? defaultIndexType : indexConfig.type || defaultIndexType;
1341
+ if (indexType === "ivfflat" && vectorType === "sparsevec") {
1342
+ throw new error.MastraError({
1343
+ id: storage.createVectorErrorId("PG", "BUILD_INDEX", "UNSUPPORTED_INDEX_TYPE"),
1344
+ text: `IVFFlat indexes do not support sparsevec type. Use HNSW instead.`,
1345
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1346
+ category: error.ErrorCategory.USER,
1347
+ details: { indexName, vectorType, indexType }
1348
+ });
1349
+ }
1350
+ if (indexType === "ivfflat" && vectorType === "bit" && metric === "jaccard") {
1351
+ throw new error.MastraError({
1352
+ id: storage.createVectorErrorId("PG", "BUILD_INDEX", "UNSUPPORTED_INDEX_TYPE"),
1353
+ text: `IVFFlat indexes do not support Jaccard distance for bit vectors. Use HNSW instead.`,
1354
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1355
+ category: error.ErrorCategory.USER,
1356
+ details: { indexName, vectorType, indexType, metric }
1357
+ });
1358
+ }
1217
1359
  const { tableName, vectorIndexName } = this.getTableName(indexName);
1218
1360
  let existingIndexInfo = null;
1219
1361
  let dimension = 0;
@@ -1271,7 +1413,7 @@ var PgVector = class extends vector.MastraVector {
1271
1413
  return;
1272
1414
  }
1273
1415
  const effectiveVectorType = existingIndexInfo?.vectorType ?? vectorType;
1274
- const metricOp = this.getMetricOperatorClass(metric, effectiveVectorType);
1416
+ const metricOp = this.getVectorOps(effectiveVectorType, metric).operatorClass;
1275
1417
  let indexSQL;
1276
1418
  if (indexType === "hnsw") {
1277
1419
  const m = indexConfig.hnsw?.m ?? 8;
@@ -1303,6 +1445,21 @@ var PgVector = class extends vector.MastraVector {
1303
1445
  await client.query(indexSQL);
1304
1446
  });
1305
1447
  }
1448
+ async createMetadataIndexes(tableName, indexName, metadataFields) {
1449
+ const hasher = await this.hasher;
1450
+ for (const field of metadataFields) {
1451
+ const fieldHash = hasher.h32(field).toString(16);
1452
+ const prefix = indexName.slice(0, 63 - "_md__idx".length - fieldHash.length);
1453
+ const metadataIdxName = `"${prefix}_md_${fieldHash}_idx"`;
1454
+ const escapedField = field.replace(/'/g, "''");
1455
+ await this.pool.query(
1456
+ `
1457
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS ${metadataIdxName}
1458
+ ON ${tableName} ((metadata->>'${escapedField}'))
1459
+ `
1460
+ );
1461
+ }
1462
+ }
1306
1463
  async installVectorExtension(client) {
1307
1464
  if (this.vectorExtensionInstalled) {
1308
1465
  return;
@@ -1389,7 +1546,7 @@ var PgVector = class extends vector.MastraVector {
1389
1546
  WHERE c.table_schema = t.table_schema
1390
1547
  AND c.table_name = t.table_name
1391
1548
  AND c.column_name = 'embedding'
1392
- AND c.udt_name IN ('vector', 'halfvec')
1549
+ AND c.udt_name IN ('vector', 'halfvec', 'bit', 'sparsevec')
1393
1550
  )
1394
1551
  AND EXISTS (
1395
1552
  SELECT 1
@@ -1432,14 +1589,15 @@ var PgVector = class extends vector.MastraVector {
1432
1589
  FROM information_schema.columns
1433
1590
  WHERE table_schema = $1
1434
1591
  AND table_name = $2
1435
- AND udt_name IN ('vector', 'halfvec')
1592
+ AND udt_name IN ('vector', 'halfvec', 'bit', 'sparsevec')
1436
1593
  LIMIT 1;
1437
1594
  `;
1438
1595
  const tableExists = await client.query(tableExistsQuery, [this.schema || "public", indexName]);
1439
1596
  if (tableExists.rows.length === 0) {
1440
1597
  throw new Error(`Vector table ${tableName} does not exist`);
1441
1598
  }
1442
- const vectorType = tableExists.rows[0].udt_name === "halfvec" ? "halfvec" : "vector";
1599
+ const udtName = tableExists.rows[0].udt_name;
1600
+ const vectorType = udtName === "halfvec" ? "halfvec" : udtName === "bit" ? "bit" : udtName === "sparsevec" ? "sparsevec" : "vector";
1443
1601
  const dimensionQuery = `
1444
1602
  SELECT atttypmod as dimension
1445
1603
  FROM pg_attribute
@@ -1471,7 +1629,7 @@ var PgVector = class extends vector.MastraVector {
1471
1629
  index_def: "",
1472
1630
  operator_class: "cosine"
1473
1631
  };
1474
- const metric = operator_class.includes("l2") ? "euclidean" : operator_class.includes("ip") ? "dotproduct" : "cosine";
1632
+ const metric = operator_class.includes("hamming") ? "hamming" : operator_class.includes("jaccard") ? "jaccard" : operator_class.includes("l2") ? "euclidean" : operator_class.includes("ip") ? "dotproduct" : "cosine";
1475
1633
  const config = {};
1476
1634
  if (index_method === "hnsw") {
1477
1635
  const m = index_def.match(/m\s*=\s*'?(\d+)'?/)?.[1];
@@ -1606,13 +1764,14 @@ var PgVector = class extends vector.MastraVector {
1606
1764
  client = await this.pool.connect();
1607
1765
  const { tableName } = this.getTableName(indexName);
1608
1766
  const indexInfo = await this.getIndexInfo({ indexName });
1609
- const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
1767
+ const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
1768
+ const ops = this.getVectorOps(indexInfo.vectorType, indexInfo.metric ?? "cosine");
1610
1769
  let updateParts = [];
1611
1770
  let values = [];
1612
1771
  let valueIndex = 1;
1613
1772
  if (update.vector) {
1614
1773
  updateParts.push(`embedding = $${valueIndex}::${qualifiedVectorType}`);
1615
- values.push(`[${update.vector.join(",")}]`);
1774
+ values.push(ops.formatVector(update.vector, indexInfo.dimension));
1616
1775
  valueIndex++;
1617
1776
  }
1618
1777
  if (update.metadata) {
@@ -2180,6 +2339,8 @@ var PgDB = class extends base.MastraBase {
2180
2339
  client;
2181
2340
  schemaName;
2182
2341
  skipDefaultIndexes;
2342
+ /** Cache of actual table columns: tableName -> Set<columnName> */
2343
+ tableColumnsCache = /* @__PURE__ */ new Map();
2183
2344
  constructor(config) {
2184
2345
  super({
2185
2346
  component: "STORAGE",
@@ -2189,6 +2350,40 @@ var PgDB = class extends base.MastraBase {
2189
2350
  this.schemaName = config.schemaName;
2190
2351
  this.skipDefaultIndexes = config.skipDefaultIndexes;
2191
2352
  }
2353
+ /**
2354
+ * Gets the set of column names that actually exist in the database table.
2355
+ * Results are cached; the cache is invalidated when alterTable() adds new columns.
2356
+ */
2357
+ async getTableColumns(tableName) {
2358
+ const cached = this.tableColumnsCache.get(tableName);
2359
+ if (cached) return cached;
2360
+ const schema = this.schemaName || "public";
2361
+ const rows = await this.client.manyOrNone(
2362
+ `SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2`,
2363
+ [schema, tableName]
2364
+ );
2365
+ const columns = new Set(rows.map((r) => r.column_name));
2366
+ if (columns.size > 0) {
2367
+ this.tableColumnsCache.set(tableName, columns);
2368
+ }
2369
+ return columns;
2370
+ }
2371
+ /**
2372
+ * Filters a record to only include columns that exist in the actual database table.
2373
+ * Unknown columns are silently dropped to ensure forward compatibility when newer
2374
+ * domain packages add fields that haven't been migrated yet.
2375
+ */
2376
+ async filterRecordToKnownColumns(tableName, record) {
2377
+ const knownColumns = await this.getTableColumns(tableName);
2378
+ if (knownColumns.size === 0) return record;
2379
+ const filtered = {};
2380
+ for (const [key, value] of Object.entries(record)) {
2381
+ if (knownColumns.has(key)) {
2382
+ filtered[key] = value;
2383
+ }
2384
+ }
2385
+ return filtered;
2386
+ }
2192
2387
  async hasColumn(table, column) {
2193
2388
  const schema = this.schemaName || "public";
2194
2389
  const result = await this.client.oneOrNone(
@@ -2305,9 +2500,11 @@ var PgDB = class extends base.MastraBase {
2305
2500
  async insert({ tableName, record }) {
2306
2501
  try {
2307
2502
  this.addTimestampZColumns(record);
2503
+ const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
2308
2504
  const schemaName = getSchemaName(this.schemaName);
2309
- const columns = Object.keys(record).map((col) => utils.parseSqlIdentifier(col, "column name"));
2310
- const values = this.prepareValuesForInsert(record, tableName);
2505
+ const columns = Object.keys(filteredRecord).map((col) => utils.parseSqlIdentifier(col, "column name"));
2506
+ if (columns.length === 0) return;
2507
+ const values = this.prepareValuesForInsert(filteredRecord, tableName);
2311
2508
  const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
2312
2509
  const fullTableName = getTableName({ indexName: tableName, schemaName });
2313
2510
  const columnList = columns.map((c) => `"${c}"`).join(", ");
@@ -2443,6 +2640,8 @@ Note: This migration may take some time for large tables.
2443
2640
  },
2444
2641
  error$1
2445
2642
  );
2643
+ } finally {
2644
+ this.tableColumnsCache.delete(tableName);
2446
2645
  }
2447
2646
  }
2448
2647
  async setupTimestampTriggers(tableName) {
@@ -2762,6 +2961,8 @@ Note: This migration may take some time for large tables.
2762
2961
  },
2763
2962
  error$1
2764
2963
  );
2964
+ } finally {
2965
+ this.tableColumnsCache.delete(tableName);
2765
2966
  }
2766
2967
  }
2767
2968
  async load({ tableName, keys }) {
@@ -2838,6 +3039,8 @@ Note: This migration may take some time for large tables.
2838
3039
  },
2839
3040
  error$1
2840
3041
  );
3042
+ } finally {
3043
+ this.tableColumnsCache.delete(tableName);
2841
3044
  }
2842
3045
  }
2843
3046
  async createIndex(options) {
@@ -3082,10 +3285,12 @@ Note: This migration may take some time for large tables.
3082
3285
  data
3083
3286
  }) {
3084
3287
  try {
3288
+ const filteredData = await this.filterRecordToKnownColumns(tableName, data);
3289
+ if (Object.keys(filteredData).length === 0) return;
3085
3290
  const setColumns = [];
3086
3291
  const setValues = [];
3087
3292
  let paramIndex = 1;
3088
- Object.entries(data).forEach(([key, value]) => {
3293
+ Object.entries(filteredData).forEach(([key, value]) => {
3089
3294
  const parsedKey = utils.parseSqlIdentifier(key, "column name");
3090
3295
  setColumns.push(`"${parsedKey}" = $${paramIndex++}`);
3091
3296
  setValues.push(this.prepareValue(value, key, tableName));
@@ -4144,9 +4349,18 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4144
4349
  compositePrimaryKey: storage.TABLE_CONFIGS[storage.TABLE_DATASET_ITEMS]?.compositePrimaryKey
4145
4350
  });
4146
4351
  await this.#db.createTable({ tableName: storage.TABLE_DATASET_VERSIONS, schema: storage.DATASET_VERSIONS_SCHEMA });
4352
+ await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "requestContextSchema", "JSONB");
4353
+ await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "requestContext", "JSONB");
4147
4354
  await this.createDefaultIndexes();
4148
4355
  await this.createCustomIndexes();
4149
4356
  }
4357
+ async #addColumnIfNotExists(table, column, sqlType) {
4358
+ const exists = await this.#db.hasColumn(table, column);
4359
+ if (!exists) {
4360
+ const fullTableName = getTableName2({ indexName: table, schemaName: getSchemaName2(this.#schema) });
4361
+ await this.#db.client.none(`ALTER TABLE ${fullTableName} ADD COLUMN "${column}" ${sqlType}`);
4362
+ }
4363
+ }
4150
4364
  getDefaultIndexDefinitions() {
4151
4365
  return [
4152
4366
  { name: "idx_dataset_items_dataset_validto", table: storage.TABLE_DATASET_ITEMS, columns: ["datasetId", "validTo"] },
@@ -4202,6 +4416,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4202
4416
  metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0,
4203
4417
  inputSchema: row.inputSchema ? storage.safelyParseJSON(row.inputSchema) : void 0,
4204
4418
  groundTruthSchema: row.groundTruthSchema ? storage.safelyParseJSON(row.groundTruthSchema) : void 0,
4419
+ requestContextSchema: row.requestContextSchema ? storage.safelyParseJSON(row.requestContextSchema) : void 0,
4205
4420
  version: row.version,
4206
4421
  createdAt: storage.ensureDate(row.createdAtZ || row.createdAt),
4207
4422
  updatedAt: storage.ensureDate(row.updatedAtZ || row.updatedAt)
@@ -4214,6 +4429,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4214
4429
  datasetVersion: row.datasetVersion,
4215
4430
  input: storage.safelyParseJSON(row.input),
4216
4431
  groundTruth: row.groundTruth ? storage.safelyParseJSON(row.groundTruth) : void 0,
4432
+ requestContext: row.requestContext ? storage.safelyParseJSON(row.requestContext) : void 0,
4217
4433
  metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0,
4218
4434
  createdAt: storage.ensureDate(row.createdAtZ || row.createdAt),
4219
4435
  updatedAt: storage.ensureDate(row.updatedAtZ || row.updatedAt)
@@ -4228,6 +4444,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4228
4444
  isDeleted: Boolean(row.isDeleted),
4229
4445
  input: storage.safelyParseJSON(row.input),
4230
4446
  groundTruth: row.groundTruth ? storage.safelyParseJSON(row.groundTruth) : void 0,
4447
+ requestContext: row.requestContext ? storage.safelyParseJSON(row.requestContext) : void 0,
4231
4448
  metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0,
4232
4449
  createdAt: storage.ensureDate(row.createdAtZ || row.createdAt),
4233
4450
  updatedAt: storage.ensureDate(row.updatedAtZ || row.updatedAt)
@@ -4256,6 +4473,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4256
4473
  metadata: input.metadata ?? null,
4257
4474
  inputSchema: input.inputSchema ?? null,
4258
4475
  groundTruthSchema: input.groundTruthSchema ?? null,
4476
+ requestContextSchema: input.requestContextSchema ?? null,
4259
4477
  version: 0,
4260
4478
  createdAt: nowIso,
4261
4479
  updatedAt: nowIso
@@ -4268,6 +4486,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4268
4486
  metadata: input.metadata,
4269
4487
  inputSchema: input.inputSchema ?? void 0,
4270
4488
  groundTruthSchema: input.groundTruthSchema ?? void 0,
4489
+ requestContextSchema: input.requestContextSchema ?? void 0,
4271
4490
  version: 0,
4272
4491
  createdAt: now,
4273
4492
  updatedAt: now
@@ -4335,6 +4554,10 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4335
4554
  setClauses.push(`"groundTruthSchema" = $${paramIndex++}`);
4336
4555
  values.push(args.groundTruthSchema === null ? null : JSON.stringify(args.groundTruthSchema));
4337
4556
  }
4557
+ if (args.requestContextSchema !== void 0) {
4558
+ setClauses.push(`"requestContextSchema" = $${paramIndex++}`);
4559
+ values.push(args.requestContextSchema === null ? null : JSON.stringify(args.requestContextSchema));
4560
+ }
4338
4561
  values.push(args.id);
4339
4562
  await this.#db.client.none(
4340
4563
  `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE "id" = $${paramIndex}`,
@@ -4347,6 +4570,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4347
4570
  metadata: args.metadata ?? existing.metadata,
4348
4571
  inputSchema: (args.inputSchema !== void 0 ? args.inputSchema : existing.inputSchema) ?? void 0,
4349
4572
  groundTruthSchema: (args.groundTruthSchema !== void 0 ? args.groundTruthSchema : existing.groundTruthSchema) ?? void 0,
4573
+ requestContextSchema: (args.requestContextSchema !== void 0 ? args.requestContextSchema : existing.requestContextSchema) ?? void 0,
4350
4574
  updatedAt: new Date(now)
4351
4575
  };
4352
4576
  } catch (error$1) {
@@ -4462,13 +4686,14 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4462
4686
  );
4463
4687
  newVersion = row.version;
4464
4688
  await t.none(
4465
- `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
4689
+ `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
4466
4690
  [
4467
4691
  id,
4468
4692
  args.datasetId,
4469
4693
  newVersion,
4470
4694
  JSON.stringify(args.input),
4471
4695
  jsonbArg(args.groundTruth),
4696
+ jsonbArg(args.requestContext),
4472
4697
  jsonbArg(args.metadata),
4473
4698
  nowIso,
4474
4699
  nowIso,
@@ -4487,6 +4712,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4487
4712
  datasetVersion: newVersion,
4488
4713
  input: args.input,
4489
4714
  groundTruth: args.groundTruth,
4715
+ requestContext: args.requestContext,
4490
4716
  metadata: args.metadata,
4491
4717
  createdAt: now,
4492
4718
  updatedAt: now
@@ -4533,6 +4759,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4533
4759
  const nowIso = now.toISOString();
4534
4760
  const mergedInput = args.input ?? existing.input;
4535
4761
  const mergedGroundTruth = args.groundTruth ?? existing.groundTruth;
4762
+ const mergedRequestContext = args.requestContext ?? existing.requestContext;
4536
4763
  const mergedMetadata = args.metadata ?? existing.metadata;
4537
4764
  let newVersion;
4538
4765
  await this.#db.client.tx(async (t) => {
@@ -4546,13 +4773,14 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4546
4773
  [newVersion, args.id]
4547
4774
  );
4548
4775
  await t.none(
4549
- `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
4776
+ `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
4550
4777
  [
4551
4778
  args.id,
4552
4779
  args.datasetId,
4553
4780
  newVersion,
4554
4781
  JSON.stringify(mergedInput),
4555
4782
  jsonbArg(mergedGroundTruth),
4783
+ jsonbArg(mergedRequestContext),
4556
4784
  jsonbArg(mergedMetadata),
4557
4785
  existing.createdAt.toISOString(),
4558
4786
  existing.createdAt.toISOString(),
@@ -4570,6 +4798,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4570
4798
  datasetVersion: newVersion,
4571
4799
  input: mergedInput,
4572
4800
  groundTruth: mergedGroundTruth,
4801
+ requestContext: mergedRequestContext,
4573
4802
  metadata: mergedMetadata,
4574
4803
  updatedAt: now
4575
4804
  };
@@ -4616,13 +4845,14 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4616
4845
  [newVersion, id]
4617
4846
  );
4618
4847
  await t.none(
4619
- `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10)`,
4848
+ `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10,$11)`,
4620
4849
  [
4621
4850
  id,
4622
4851
  datasetId,
4623
4852
  newVersion,
4624
4853
  JSON.stringify(existing.input),
4625
4854
  jsonbArg(existing.groundTruth),
4855
+ jsonbArg(existing.requestContext),
4626
4856
  jsonbArg(existing.metadata),
4627
4857
  existing.createdAt.toISOString(),
4628
4858
  existing.createdAt.toISOString(),
@@ -4677,13 +4907,14 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4677
4907
  newVersion = row.version;
4678
4908
  for (const { id, input: itemInput } of itemsWithIds) {
4679
4909
  await t.none(
4680
- `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
4910
+ `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
4681
4911
  [
4682
4912
  id,
4683
4913
  input.datasetId,
4684
4914
  newVersion,
4685
4915
  JSON.stringify(itemInput.input),
4686
4916
  jsonbArg(itemInput.groundTruth),
4917
+ jsonbArg(itemInput.requestContext),
4687
4918
  jsonbArg(itemInput.metadata),
4688
4919
  nowIso,
4689
4920
  nowIso,
@@ -4703,6 +4934,7 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4703
4934
  datasetVersion: newVersion,
4704
4935
  input: itemInput.input,
4705
4936
  groundTruth: itemInput.groundTruth,
4937
+ requestContext: itemInput.requestContext,
4706
4938
  metadata: itemInput.metadata,
4707
4939
  createdAt: now,
4708
4940
  updatedAt: now
@@ -4758,13 +4990,14 @@ var DatasetsPG = class _DatasetsPG extends storage.DatasetsStorage {
4758
4990
  [newVersion, item.id]
4759
4991
  );
4760
4992
  await t.none(
4761
- `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10)`,
4993
+ `INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10,$11)`,
4762
4994
  [
4763
4995
  item.id,
4764
4996
  input.datasetId,
4765
4997
  newVersion,
4766
4998
  JSON.stringify(item.input),
4767
4999
  jsonbArg(item.groundTruth),
5000
+ jsonbArg(item.requestContext),
4768
5001
  jsonbArg(item.metadata),
4769
5002
  item.createdAt.toISOString(),
4770
5003
  item.createdAt.toISOString(),
@@ -7171,39 +7404,73 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7171
7404
  * issues on large tables (see GitHub issue #11150). The old approach required
7172
7405
  * scanning and sorting ALL messages in a thread to assign row numbers.
7173
7406
  *
7174
- * The new approach uses the existing (thread_id, createdAt) index to efficiently
7175
- * fetch only the messages needed by using createdAt as a cursor.
7407
+ * The current approach uses two phases for optimal performance:
7408
+ * 1. Batch-fetch all target messages' metadata (thread_id, createdAt) in one query
7409
+ * 2. Build cursor subqueries using "createdAt" directly (not COALESCE) so that
7410
+ * the existing (thread_id, createdAt DESC) index can be used for index scans
7411
+ * instead of sequential scans. This fixes GitHub issue #11702 where semantic
7412
+ * recall latency scaled linearly with message count (~30s for 7.4k messages).
7176
7413
  */
7414
+ _sortMessages(messages, field, direction) {
7415
+ return messages.sort((a, b) => {
7416
+ const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
7417
+ const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
7418
+ if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
7419
+ if (aValue == null) return 1;
7420
+ if (bValue == null) return -1;
7421
+ if (aValue === bValue) {
7422
+ return a.id.localeCompare(b.id);
7423
+ }
7424
+ if (typeof aValue === "number" && typeof bValue === "number") {
7425
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
7426
+ }
7427
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
7428
+ });
7429
+ }
7177
7430
  async _getIncludedMessages({ include }) {
7178
7431
  if (!include || include.length === 0) return null;
7179
7432
  const tableName = getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
7180
7433
  const selectColumns = `id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
7434
+ const targetIds = include.map((inc) => inc.id).filter(Boolean);
7435
+ if (targetIds.length === 0) return null;
7436
+ const idPlaceholders = targetIds.map((_, i) => "$" + (i + 1)).join(", ");
7437
+ const targetRows = await this.#db.client.manyOrNone(`SELECT id, thread_id, "createdAt" FROM ${tableName} WHERE id IN (${idPlaceholders})`, targetIds);
7438
+ if (targetRows.length === 0) return null;
7439
+ const targetMap = new Map(targetRows.map((r) => [r.id, { threadId: r.thread_id, createdAt: r.createdAt }]));
7181
7440
  const unionQueries = [];
7182
7441
  const params = [];
7183
7442
  let paramIdx = 1;
7184
7443
  for (const inc of include) {
7185
7444
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
7445
+ const target = targetMap.get(id);
7446
+ if (!target) continue;
7447
+ const p1 = "$" + paramIdx;
7448
+ const p2 = "$" + (paramIdx + 1);
7449
+ const p3 = "$" + (paramIdx + 2);
7186
7450
  unionQueries.push(`(
7187
7451
  SELECT ${selectColumns}
7188
7452
  FROM ${tableName} m
7189
- WHERE m.thread_id = (SELECT thread_id FROM ${tableName} WHERE id = $${paramIdx})
7190
- AND COALESCE(m."createdAtZ", m."createdAt") <= (SELECT COALESCE("createdAtZ", "createdAt") FROM ${tableName} WHERE id = $${paramIdx})
7191
- ORDER BY COALESCE(m."createdAtZ", m."createdAt") DESC
7192
- LIMIT $${paramIdx + 1}
7453
+ WHERE m.thread_id = ${p1}
7454
+ AND m."createdAt" <= ${p2}
7455
+ ORDER BY m."createdAt" DESC, m.id DESC
7456
+ LIMIT ${p3}
7193
7457
  )`);
7194
- params.push(id, withPreviousMessages + 1);
7195
- paramIdx += 2;
7458
+ params.push(target.threadId, target.createdAt, withPreviousMessages + 1);
7459
+ paramIdx += 3;
7196
7460
  if (withNextMessages > 0) {
7461
+ const p4 = "$" + paramIdx;
7462
+ const p5 = "$" + (paramIdx + 1);
7463
+ const p6 = "$" + (paramIdx + 2);
7197
7464
  unionQueries.push(`(
7198
7465
  SELECT ${selectColumns}
7199
7466
  FROM ${tableName} m
7200
- WHERE m.thread_id = (SELECT thread_id FROM ${tableName} WHERE id = $${paramIdx})
7201
- AND COALESCE(m."createdAtZ", m."createdAt") > (SELECT COALESCE("createdAtZ", "createdAt") FROM ${tableName} WHERE id = $${paramIdx})
7202
- ORDER BY COALESCE(m."createdAtZ", m."createdAt") ASC
7203
- LIMIT $${paramIdx + 1}
7467
+ WHERE m.thread_id = ${p4}
7468
+ AND m."createdAt" > ${p5}
7469
+ ORDER BY m."createdAt" ASC, m.id ASC
7470
+ LIMIT ${p6}
7204
7471
  )`);
7205
- params.push(id, withNextMessages);
7206
- paramIdx += 2;
7472
+ params.push(target.threadId, target.createdAt, withNextMessages);
7473
+ paramIdx += 3;
7207
7474
  }
7208
7475
  }
7209
7476
  if (unionQueries.length === 0) return null;
@@ -7211,7 +7478,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7211
7478
  if (unionQueries.length === 1) {
7212
7479
  finalQuery = unionQueries[0].slice(1, -1);
7213
7480
  } else {
7214
- finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) AS combined ORDER BY "createdAt" ASC`;
7481
+ finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) AS combined ORDER BY "createdAt" ASC, id ASC`;
7215
7482
  }
7216
7483
  const includedRows = await this.#db.client.manyOrNone(finalQuery, params);
7217
7484
  const seen = /* @__PURE__ */ new Set();
@@ -7325,6 +7592,24 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7325
7592
  queryParams.push(filter.dateRange.end);
7326
7593
  }
7327
7594
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
7595
+ if (perPage === 0 && (!include || include.length === 0)) {
7596
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
7597
+ }
7598
+ if (perPage === 0 && include && include.length > 0) {
7599
+ const includeMessages = await this._getIncludedMessages({ include });
7600
+ if (!includeMessages || includeMessages.length === 0) {
7601
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
7602
+ }
7603
+ const messagesWithParsedContent2 = includeMessages.map((row) => this.parseRow(row));
7604
+ const list2 = new agent.MessageList().add(messagesWithParsedContent2, "memory");
7605
+ return {
7606
+ messages: this._sortMessages(list2.get.all.db(), field, direction),
7607
+ total: 0,
7608
+ page,
7609
+ perPage: perPageForResponse,
7610
+ hasMore: false
7611
+ };
7612
+ }
7328
7613
  const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
7329
7614
  const countResult = await this.#db.client.one(countQuery, queryParams);
7330
7615
  const total = parseInt(countResult.count, 10);
@@ -7355,21 +7640,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7355
7640
  }
7356
7641
  const messagesWithParsedContent = messages.map((row) => this.parseRow(row));
7357
7642
  const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
7358
- let finalMessages = list.get.all.db();
7359
- finalMessages = finalMessages.sort((a, b) => {
7360
- const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
7361
- const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
7362
- if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
7363
- if (aValue == null) return 1;
7364
- if (bValue == null) return -1;
7365
- if (aValue === bValue) {
7366
- return a.id.localeCompare(b.id);
7367
- }
7368
- if (typeof aValue === "number" && typeof bValue === "number") {
7369
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
7370
- }
7371
- return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
7372
- });
7643
+ const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
7373
7644
  const threadIdSet = new Set(threadIds);
7374
7645
  const returnedThreadMessageIds = new Set(
7375
7646
  finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
@@ -7458,6 +7729,30 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7458
7729
  queryParams.push(filter.dateRange.end);
7459
7730
  }
7460
7731
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
7732
+ if (perPage === 0 && (!include || include.length === 0)) {
7733
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
7734
+ }
7735
+ if (perPage === 0 && include && include.length > 0) {
7736
+ const includeMessages = await this._getIncludedMessages({ include });
7737
+ if (!includeMessages || includeMessages.length === 0) {
7738
+ return {
7739
+ messages: [],
7740
+ total: 0,
7741
+ page,
7742
+ perPage: perPageForResponse,
7743
+ hasMore: false
7744
+ };
7745
+ }
7746
+ const messagesWithParsedContent2 = includeMessages.map((row) => this.parseRow(row));
7747
+ const list2 = new agent.MessageList().add(messagesWithParsedContent2, "memory");
7748
+ return {
7749
+ messages: this._sortMessages(list2.get.all.db(), field, direction),
7750
+ total: 0,
7751
+ page,
7752
+ perPage: perPageForResponse,
7753
+ hasMore: false
7754
+ };
7755
+ }
7461
7756
  const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
7462
7757
  const countResult = await this.#db.client.one(countQuery, queryParams);
7463
7758
  const total = parseInt(countResult.count, 10);
@@ -7488,21 +7783,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
7488
7783
  }
7489
7784
  const messagesWithParsedContent = messages.map((row) => this.parseRow(row));
7490
7785
  const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
7491
- let finalMessages = list.get.all.db();
7492
- finalMessages = finalMessages.sort((a, b) => {
7493
- const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
7494
- const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
7495
- if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
7496
- if (aValue == null) return 1;
7497
- if (bValue == null) return -1;
7498
- if (aValue === bValue) {
7499
- return a.id.localeCompare(b.id);
7500
- }
7501
- if (typeof aValue === "number" && typeof bValue === "number") {
7502
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
7503
- }
7504
- return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
7505
- });
7786
+ const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
7506
7787
  const hasMore = perPageInput !== false && offset + perPage < total;
7507
7788
  return {
7508
7789
  messages: finalMessages,
@@ -8956,6 +9237,11 @@ var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorag
8956
9237
  }
8957
9238
  async init() {
8958
9239
  await this.#db.createTable({ tableName: storage.TABLE_SPANS, schema: storage.TABLE_SCHEMAS[storage.TABLE_SPANS] });
9240
+ await this.#db.alterTable({
9241
+ tableName: storage.TABLE_SPANS,
9242
+ schema: storage.TABLE_SCHEMAS[storage.TABLE_SPANS],
9243
+ ifNotExists: ["requestContext"]
9244
+ });
8959
9245
  await this.createDefaultIndexes();
8960
9246
  await this.createCustomIndexes();
8961
9247
  }
@@ -9310,7 +9596,8 @@ var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorag
9310
9596
  }
9311
9597
  async listTraces(args) {
9312
9598
  const { filters, pagination, orderBy } = storage.listTracesArgsSchema.parse(args);
9313
- const { page, perPage } = pagination;
9599
+ const page = pagination?.page ?? 0;
9600
+ const perPage = pagination?.perPage ?? 10;
9314
9601
  const tableName = getTableName2({
9315
9602
  indexName: storage.TABLE_SPANS,
9316
9603
  schemaName: getSchemaName2(this.#schema)
@@ -9432,10 +9719,11 @@ var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorag
9432
9719
  }
9433
9720
  }
9434
9721
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
9435
- const sortField = `${orderBy.field}Z`;
9436
- const sortDirection = orderBy.direction;
9722
+ const orderField = orderBy?.field ?? "startedAt";
9723
+ const sortField = `${orderField}Z`;
9724
+ const sortDirection = orderBy?.direction ?? "DESC";
9437
9725
  let orderClause;
9438
- if (orderBy.field === "endedAt") {
9726
+ if (orderField === "endedAt") {
9439
9727
  const nullsOrder = sortDirection === "DESC" ? "NULLS FIRST" : "NULLS LAST";
9440
9728
  orderClause = `ORDER BY r."${sortField}" ${sortDirection} ${nullsOrder}`;
9441
9729
  } else {