@mastra/pg 1.0.0-beta.9 → 1.1.0-alpha.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 (43) hide show
  1. package/CHANGELOG.md +1481 -0
  2. package/dist/docs/README.md +36 -0
  3. package/dist/docs/SKILL.md +37 -0
  4. package/dist/docs/SOURCE_MAP.json +6 -0
  5. package/dist/docs/memory/01-storage.md +261 -0
  6. package/dist/docs/memory/02-working-memory.md +411 -0
  7. package/dist/docs/memory/03-semantic-recall.md +256 -0
  8. package/dist/docs/memory/04-reference.md +133 -0
  9. package/dist/docs/processors/01-reference.md +296 -0
  10. package/dist/docs/rag/01-overview.md +74 -0
  11. package/dist/docs/rag/02-vector-databases.md +643 -0
  12. package/dist/docs/rag/03-retrieval.md +548 -0
  13. package/dist/docs/rag/04-reference.md +369 -0
  14. package/dist/docs/storage/01-reference.md +905 -0
  15. package/dist/docs/tools/01-reference.md +440 -0
  16. package/dist/docs/vectors/01-reference.md +307 -0
  17. package/dist/index.cjs +1293 -260
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1290 -262
  22. package/dist/index.js.map +1 -1
  23. package/dist/shared/config.d.ts +61 -66
  24. package/dist/shared/config.d.ts.map +1 -1
  25. package/dist/storage/client.d.ts +91 -0
  26. package/dist/storage/client.d.ts.map +1 -0
  27. package/dist/storage/db/index.d.ts +82 -17
  28. package/dist/storage/db/index.d.ts.map +1 -1
  29. package/dist/storage/domains/agents/index.d.ts +11 -1
  30. package/dist/storage/domains/agents/index.d.ts.map +1 -1
  31. package/dist/storage/domains/memory/index.d.ts +3 -2
  32. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  33. package/dist/storage/domains/observability/index.d.ts +24 -1
  34. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  35. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  36. package/dist/storage/domains/workflows/index.d.ts +1 -0
  37. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  38. package/dist/storage/index.d.ts +44 -17
  39. package/dist/storage/index.d.ts.map +1 -1
  40. package/dist/storage/test-utils.d.ts.map +1 -1
  41. package/dist/vector/index.d.ts.map +1 -1
  42. package/dist/vector/sql-builder.d.ts.map +1 -1
  43. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { createVectorErrorId, MastraStorage, createStorageErrorId, ScoresStorage, TABLE_SCORERS, TABLE_SCHEMAS, normalizePerPage, calculatePagination, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, AgentsStorage, TABLE_AGENTS, getSqlType, getDefaultValue, transformScoreRow as transformScoreRow$1, TraceStatus } from '@mastra/core/storage';
2
+ import { createVectorErrorId, TABLE_SCHEMAS, AgentsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, createStorageErrorId, normalizePerPage, calculatePagination, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, toTraceSpans, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraCompositeStore, TraceStatus, getDefaultValue, transformScoreRow as transformScoreRow$1, getSqlType } from '@mastra/core/storage';
3
3
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
- import { MastraVector } from '@mastra/core/vector';
4
+ import { MastraVector, validateTopK, validateUpsertInput } from '@mastra/core/vector';
5
5
  import { Mutex } from 'async-mutex';
6
6
  import * as pg from 'pg';
7
+ import { Pool } from 'pg';
7
8
  import xxhash from 'xxhash-wasm';
8
9
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
9
- import pgPromise from 'pg-promise';
10
10
  import { MastraBase } from '@mastra/core/base';
11
11
  import { MessageList } from '@mastra/core/agent';
12
12
  import { saveScorePayloadSchema } from '@mastra/core/evals';
@@ -14,8 +14,11 @@ import { saveScorePayloadSchema } from '@mastra/core/evals';
14
14
  // src/vector/index.ts
15
15
 
16
16
  // src/shared/config.ts
17
+ var isPoolConfig = (cfg) => {
18
+ return "pool" in cfg;
19
+ };
17
20
  var isConnectionStringConfig = (cfg) => {
18
- return "connectionString" in cfg;
21
+ return "connectionString" in cfg && typeof cfg.connectionString === "string";
19
22
  };
20
23
  var isHostConfig = (cfg) => {
21
24
  return "host" in cfg && "database" in cfg && "user" in cfg && "password" in cfg;
@@ -23,16 +26,13 @@ var isHostConfig = (cfg) => {
23
26
  var isCloudSqlConfig = (cfg) => {
24
27
  return "stream" in cfg || "password" in cfg && typeof cfg.password === "function";
25
28
  };
26
- var isClientConfig = (cfg) => {
27
- return "client" in cfg;
28
- };
29
29
  var validateConfig = (name, config) => {
30
30
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
31
31
  throw new Error(`${name}: id must be provided and cannot be empty.`);
32
32
  }
33
- if (isClientConfig(config)) {
34
- if (!config.client) {
35
- throw new Error(`${name}: client must be provided when using client config.`);
33
+ if (isPoolConfig(config)) {
34
+ if (!config.pool) {
35
+ throw new Error(`${name}: pool must be provided when using pool config.`);
36
36
  }
37
37
  return;
38
38
  }
@@ -53,7 +53,7 @@ var validateConfig = (name, config) => {
53
53
  }
54
54
  } else {
55
55
  throw new Error(
56
- `${name}: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with \`stream\`).`
56
+ `${name}: invalid config. Provide either {pool}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with \`stream\`).`
57
57
  );
58
58
  }
59
59
  };
@@ -261,8 +261,14 @@ var FILTER_OPERATORS = {
261
261
  };
262
262
  },
263
263
  // Element Operators
264
- $exists: (key) => {
264
+ $exists: (key, paramIndex, value) => {
265
265
  const jsonPathKey = parseJsonPathKey(key);
266
+ if (value === false) {
267
+ return {
268
+ sql: `NOT (metadata ? '${jsonPathKey}')`,
269
+ needsValue: false
270
+ };
271
+ }
266
272
  return {
267
273
  sql: `metadata ? '${jsonPathKey}'`,
268
274
  needsValue: false
@@ -340,17 +346,62 @@ function buildDeleteFilterQuery(filter) {
340
346
  values.push(value);
341
347
  return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
342
348
  }
343
- const [[operator, operatorValue] = []] = Object.entries(value);
349
+ const entries = Object.entries(value);
350
+ if (entries.length > 1) {
351
+ const conditions2 = entries.map(([operator2, operatorValue2]) => {
352
+ if (operator2 === "$not") {
353
+ const nestedEntries = Object.entries(operatorValue2);
354
+ const nestedConditions = nestedEntries.map(([nestedOp, nestedValue]) => {
355
+ if (!FILTER_OPERATORS[nestedOp]) {
356
+ throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
357
+ }
358
+ const operatorFn3 = FILTER_OPERATORS[nestedOp];
359
+ const operatorResult3 = operatorFn3(key, values.length + 1, nestedValue);
360
+ if (operatorResult3.needsValue) {
361
+ const transformedValue = operatorResult3.transformValue ? operatorResult3.transformValue() : nestedValue;
362
+ if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
363
+ values.push(...transformedValue);
364
+ } else {
365
+ values.push(transformedValue);
366
+ }
367
+ }
368
+ return operatorResult3.sql;
369
+ }).join(" AND ");
370
+ return `NOT (${nestedConditions})`;
371
+ }
372
+ if (!FILTER_OPERATORS[operator2]) {
373
+ throw new Error(`Invalid operator: ${operator2}`);
374
+ }
375
+ const operatorFn2 = FILTER_OPERATORS[operator2];
376
+ const operatorResult2 = operatorFn2(key, values.length + 1, operatorValue2);
377
+ if (operatorResult2.needsValue) {
378
+ const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : operatorValue2;
379
+ if (Array.isArray(transformedValue) && operator2 === "$elemMatch") {
380
+ values.push(...transformedValue);
381
+ } else {
382
+ values.push(transformedValue);
383
+ }
384
+ }
385
+ return operatorResult2.sql;
386
+ });
387
+ return conditions2.join(" AND ");
388
+ }
389
+ const [[operator, operatorValue] = []] = entries;
344
390
  if (operator === "$not") {
345
- const entries = Object.entries(operatorValue);
346
- const conditions2 = entries.map(([nestedOp, nestedValue]) => {
391
+ const nestedEntries = Object.entries(operatorValue);
392
+ const conditions2 = nestedEntries.map(([nestedOp, nestedValue]) => {
347
393
  if (!FILTER_OPERATORS[nestedOp]) {
348
394
  throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
349
395
  }
350
396
  const operatorFn2 = FILTER_OPERATORS[nestedOp];
351
397
  const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
352
398
  if (operatorResult2.needsValue) {
353
- values.push(nestedValue);
399
+ const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : nestedValue;
400
+ if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
401
+ values.push(...transformedValue);
402
+ } else {
403
+ values.push(transformedValue);
404
+ }
354
405
  }
355
406
  return operatorResult2.sql;
356
407
  }).join(" AND ");
@@ -417,17 +468,62 @@ function buildFilterQuery(filter, minScore, topK) {
417
468
  values.push(value);
418
469
  return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
419
470
  }
420
- const [[operator, operatorValue] = []] = Object.entries(value);
471
+ const entries = Object.entries(value);
472
+ if (entries.length > 1) {
473
+ const conditions2 = entries.map(([operator2, operatorValue2]) => {
474
+ if (operator2 === "$not") {
475
+ const nestedEntries = Object.entries(operatorValue2);
476
+ const nestedConditions = nestedEntries.map(([nestedOp, nestedValue]) => {
477
+ if (!FILTER_OPERATORS[nestedOp]) {
478
+ throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
479
+ }
480
+ const operatorFn3 = FILTER_OPERATORS[nestedOp];
481
+ const operatorResult3 = operatorFn3(key, values.length + 1, nestedValue);
482
+ if (operatorResult3.needsValue) {
483
+ const transformedValue = operatorResult3.transformValue ? operatorResult3.transformValue() : nestedValue;
484
+ if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
485
+ values.push(...transformedValue);
486
+ } else {
487
+ values.push(transformedValue);
488
+ }
489
+ }
490
+ return operatorResult3.sql;
491
+ }).join(" AND ");
492
+ return `NOT (${nestedConditions})`;
493
+ }
494
+ if (!FILTER_OPERATORS[operator2]) {
495
+ throw new Error(`Invalid operator: ${operator2}`);
496
+ }
497
+ const operatorFn2 = FILTER_OPERATORS[operator2];
498
+ const operatorResult2 = operatorFn2(key, values.length + 1, operatorValue2);
499
+ if (operatorResult2.needsValue) {
500
+ const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : operatorValue2;
501
+ if (Array.isArray(transformedValue) && operator2 === "$elemMatch") {
502
+ values.push(...transformedValue);
503
+ } else {
504
+ values.push(transformedValue);
505
+ }
506
+ }
507
+ return operatorResult2.sql;
508
+ });
509
+ return conditions2.join(" AND ");
510
+ }
511
+ const [[operator, operatorValue] = []] = entries;
421
512
  if (operator === "$not") {
422
- const entries = Object.entries(operatorValue);
423
- const conditions2 = entries.map(([nestedOp, nestedValue]) => {
513
+ const nestedEntries = Object.entries(operatorValue);
514
+ const conditions2 = nestedEntries.map(([nestedOp, nestedValue]) => {
424
515
  if (!FILTER_OPERATORS[nestedOp]) {
425
516
  throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
426
517
  }
427
518
  const operatorFn2 = FILTER_OPERATORS[nestedOp];
428
519
  const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
429
520
  if (operatorResult2.needsValue) {
430
- values.push(nestedValue);
521
+ const transformedValue = operatorResult2.transformValue ? operatorResult2.transformValue() : nestedValue;
522
+ if (Array.isArray(transformedValue) && nestedOp === "$elemMatch") {
523
+ values.push(...transformedValue);
524
+ } else {
525
+ values.push(transformedValue);
526
+ }
431
527
  }
432
528
  return operatorResult2.sql;
433
529
  }).join(" AND ");
@@ -518,8 +614,8 @@ var PgVector = class extends MastraVector {
518
614
  } else if (isCloudSqlConfig(config)) {
519
615
  poolConfig = {
520
616
  ...config,
521
- max: config.max ?? 20,
522
- idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
617
+ max: config.pgPoolOptions?.max ?? 20,
618
+ idleTimeoutMillis: config.pgPoolOptions?.idleTimeoutMillis ?? 3e4,
523
619
  connectionTimeoutMillis: 2e3,
524
620
  ...config.pgPoolOptions
525
621
  };
@@ -687,9 +783,7 @@ var PgVector = class extends MastraVector {
687
783
  probes
688
784
  }) {
689
785
  try {
690
- if (!Number.isInteger(topK) || topK <= 0) {
691
- throw new Error("topK must be a positive integer");
692
- }
786
+ validateTopK("PG", topK);
693
787
  if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
694
788
  throw new Error("queryVector must be an array of finite numbers");
695
789
  }
@@ -774,6 +868,7 @@ var PgVector = class extends MastraVector {
774
868
  ids,
775
869
  deleteFilter
776
870
  }) {
871
+ validateUpsertInput("PG", vectors, metadata, ids);
777
872
  const { tableName } = this.getTableName(indexName);
778
873
  const client = await this.pool.connect();
779
874
  try {
@@ -1661,6 +1756,132 @@ var PgVector = class extends MastraVector {
1661
1756
  }
1662
1757
  }
1663
1758
  };
1759
+
1760
+ // src/storage/client.ts
1761
+ function truncateQuery(query, maxLength = 100) {
1762
+ const normalized = query.replace(/\s+/g, " ").trim();
1763
+ if (normalized.length <= maxLength) {
1764
+ return normalized;
1765
+ }
1766
+ return normalized.slice(0, maxLength) + "...";
1767
+ }
1768
+ var PoolAdapter = class {
1769
+ constructor($pool) {
1770
+ this.$pool = $pool;
1771
+ }
1772
+ connect() {
1773
+ return this.$pool.connect();
1774
+ }
1775
+ async none(query, values) {
1776
+ await this.$pool.query(query, values);
1777
+ return null;
1778
+ }
1779
+ async one(query, values) {
1780
+ const result = await this.$pool.query(query, values);
1781
+ if (result.rows.length === 0) {
1782
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1783
+ }
1784
+ if (result.rows.length > 1) {
1785
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1786
+ }
1787
+ return result.rows[0];
1788
+ }
1789
+ async oneOrNone(query, values) {
1790
+ const result = await this.$pool.query(query, values);
1791
+ if (result.rows.length === 0) {
1792
+ return null;
1793
+ }
1794
+ if (result.rows.length > 1) {
1795
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1796
+ }
1797
+ return result.rows[0];
1798
+ }
1799
+ async any(query, values) {
1800
+ const result = await this.$pool.query(query, values);
1801
+ return result.rows;
1802
+ }
1803
+ async manyOrNone(query, values) {
1804
+ return this.any(query, values);
1805
+ }
1806
+ async many(query, values) {
1807
+ const result = await this.$pool.query(query, values);
1808
+ if (result.rows.length === 0) {
1809
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1810
+ }
1811
+ return result.rows;
1812
+ }
1813
+ async query(query, values) {
1814
+ return this.$pool.query(query, values);
1815
+ }
1816
+ async tx(callback) {
1817
+ const client = await this.$pool.connect();
1818
+ try {
1819
+ await client.query("BEGIN");
1820
+ const txClient = new TransactionClient(client);
1821
+ const result = await callback(txClient);
1822
+ await client.query("COMMIT");
1823
+ return result;
1824
+ } catch (error) {
1825
+ try {
1826
+ await client.query("ROLLBACK");
1827
+ } catch (rollbackError) {
1828
+ console.error("Transaction rollback failed:", rollbackError);
1829
+ }
1830
+ throw error;
1831
+ } finally {
1832
+ client.release();
1833
+ }
1834
+ }
1835
+ };
1836
+ var TransactionClient = class {
1837
+ constructor(client) {
1838
+ this.client = client;
1839
+ }
1840
+ async none(query, values) {
1841
+ await this.client.query(query, values);
1842
+ return null;
1843
+ }
1844
+ async one(query, values) {
1845
+ const result = await this.client.query(query, values);
1846
+ if (result.rows.length === 0) {
1847
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1848
+ }
1849
+ if (result.rows.length > 1) {
1850
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1851
+ }
1852
+ return result.rows[0];
1853
+ }
1854
+ async oneOrNone(query, values) {
1855
+ const result = await this.client.query(query, values);
1856
+ if (result.rows.length === 0) {
1857
+ return null;
1858
+ }
1859
+ if (result.rows.length > 1) {
1860
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1861
+ }
1862
+ return result.rows[0];
1863
+ }
1864
+ async any(query, values) {
1865
+ const result = await this.client.query(query, values);
1866
+ return result.rows;
1867
+ }
1868
+ async manyOrNone(query, values) {
1869
+ return this.any(query, values);
1870
+ }
1871
+ async many(query, values) {
1872
+ const result = await this.client.query(query, values);
1873
+ if (result.rows.length === 0) {
1874
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1875
+ }
1876
+ return result.rows;
1877
+ }
1878
+ async query(query, values) {
1879
+ return this.client.query(query, values);
1880
+ }
1881
+ async batch(promises) {
1882
+ return Promise.all(promises);
1883
+ }
1884
+ };
1664
1885
  function resolvePgConfig(config) {
1665
1886
  if ("client" in config) {
1666
1887
  return {
@@ -1670,10 +1891,32 @@ function resolvePgConfig(config) {
1670
1891
  indexes: config.indexes
1671
1892
  };
1672
1893
  }
1673
- const pgp = pgPromise();
1674
- const client = pgp(config);
1894
+ if ("pool" in config) {
1895
+ return {
1896
+ client: new PoolAdapter(config.pool),
1897
+ schemaName: config.schemaName,
1898
+ skipDefaultIndexes: config.skipDefaultIndexes,
1899
+ indexes: config.indexes
1900
+ };
1901
+ }
1902
+ let pool;
1903
+ if ("connectionString" in config) {
1904
+ pool = new Pool({
1905
+ connectionString: config.connectionString,
1906
+ ssl: config.ssl
1907
+ });
1908
+ } else {
1909
+ pool = new Pool({
1910
+ host: config.host,
1911
+ port: config.port,
1912
+ database: config.database,
1913
+ user: config.user,
1914
+ password: config.password,
1915
+ ssl: config.ssl
1916
+ });
1917
+ }
1675
1918
  return {
1676
- client,
1919
+ client: new PoolAdapter(pool),
1677
1920
  schemaName: config.schemaName,
1678
1921
  skipDefaultIndexes: config.skipDefaultIndexes,
1679
1922
  indexes: config.indexes
@@ -1688,6 +1931,91 @@ function getTableName({ indexName, schemaName }) {
1688
1931
  const quotedSchemaName = schemaName;
1689
1932
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1690
1933
  }
1934
+ function mapToSqlType(type) {
1935
+ switch (type) {
1936
+ case "uuid":
1937
+ return "UUID";
1938
+ case "boolean":
1939
+ return "BOOLEAN";
1940
+ default:
1941
+ return getSqlType(type);
1942
+ }
1943
+ }
1944
+ function generateTableSQL({
1945
+ tableName,
1946
+ schema,
1947
+ schemaName,
1948
+ includeAllConstraints = false
1949
+ }) {
1950
+ const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
1951
+ const parsedName = parseSqlIdentifier(name, "column name");
1952
+ return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
1953
+ });
1954
+ const columns = Object.entries(schema).map(([name, def]) => {
1955
+ const parsedName = parseSqlIdentifier(name, "column name");
1956
+ const constraints = [];
1957
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
1958
+ if (!def.nullable) constraints.push("NOT NULL");
1959
+ return `"${parsedName}" ${mapToSqlType(def.type)} ${constraints.join(" ")}`;
1960
+ });
1961
+ const finalColumns = [...columns, ...timeZColumns].join(",\n");
1962
+ const parsedSchemaName = schemaName ? parseSqlIdentifier(schemaName, "schema name") : "";
1963
+ const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
1964
+ const quotedSchemaName = getSchemaName(schemaName);
1965
+ const sql = `
1966
+ CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })} (
1967
+ ${finalColumns}
1968
+ );
1969
+ ${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
1970
+ DO $$ BEGIN
1971
+ IF NOT EXISTS (
1972
+ SELECT 1 FROM pg_constraint WHERE conname = lower('${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key')
1973
+ ) AND NOT EXISTS (
1974
+ SELECT 1 FROM pg_indexes WHERE indexname = lower('${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key')
1975
+ ) THEN
1976
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1977
+ ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
1978
+ UNIQUE (workflow_name, run_id);
1979
+ END IF;
1980
+ END $$;
1981
+ ` : ""}
1982
+ ${// For spans table: Include PRIMARY KEY in exports, but not in runtime (handled after deduplication)
1983
+ tableName === TABLE_SPANS && includeAllConstraints ? `
1984
+ DO $$ BEGIN
1985
+ IF NOT EXISTS (
1986
+ SELECT 1 FROM pg_constraint WHERE conname = lower('${constraintPrefix}mastra_ai_spans_traceid_spanid_pk')
1987
+ ) THEN
1988
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1989
+ ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
1990
+ PRIMARY KEY ("traceId", "spanId");
1991
+ END IF;
1992
+ END $$;
1993
+ ` : ""}
1994
+ `;
1995
+ return sql;
1996
+ }
1997
+ function exportSchemas(schemaName) {
1998
+ const statements = [];
1999
+ if (schemaName) {
2000
+ const quotedSchemaName = getSchemaName(schemaName);
2001
+ statements.push(`-- Create schema if it doesn't exist`);
2002
+ statements.push(`CREATE SCHEMA IF NOT EXISTS ${quotedSchemaName};`);
2003
+ statements.push("");
2004
+ }
2005
+ for (const [tableName, schema] of Object.entries(TABLE_SCHEMAS)) {
2006
+ statements.push(`-- Table: ${tableName}`);
2007
+ const sql = generateTableSQL({
2008
+ tableName,
2009
+ schema,
2010
+ schemaName,
2011
+ includeAllConstraints: true
2012
+ // Include all constraints for exports/documentation
2013
+ });
2014
+ statements.push(sql.trim());
2015
+ statements.push("");
2016
+ }
2017
+ return statements.join("\n");
2018
+ }
1691
2019
  var schemaSetupRegistry = /* @__PURE__ */ new Map();
1692
2020
  var PgDB = class extends MastraBase {
1693
2021
  client;
@@ -1805,16 +2133,6 @@ var PgDB = class extends MastraBase {
1805
2133
  }
1806
2134
  await registryEntry.promise;
1807
2135
  }
1808
- getSqlType(type) {
1809
- switch (type) {
1810
- case "uuid":
1811
- return "UUID";
1812
- case "boolean":
1813
- return "BOOLEAN";
1814
- default:
1815
- return getSqlType(type);
1816
- }
1817
- }
1818
2136
  getDefaultValue(type) {
1819
2137
  switch (type) {
1820
2138
  case "timestamp":
@@ -1832,10 +2150,27 @@ var PgDB = class extends MastraBase {
1832
2150
  const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1833
2151
  const values = this.prepareValuesForInsert(record, tableName);
1834
2152
  const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
1835
- await this.client.none(
1836
- `INSERT INTO ${getTableName({ indexName: tableName, schemaName })} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
1837
- values
1838
- );
2153
+ const fullTableName = getTableName({ indexName: tableName, schemaName });
2154
+ const columnList = columns.map((c) => `"${c}"`).join(", ");
2155
+ if (tableName === TABLE_SPANS) {
2156
+ const updateColumns = columns.filter((c) => c !== "traceId" && c !== "spanId");
2157
+ if (updateColumns.length > 0) {
2158
+ const updateClause = updateColumns.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ");
2159
+ await this.client.none(
2160
+ `INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})
2161
+ ON CONFLICT ("traceId", "spanId") DO UPDATE SET ${updateClause}`,
2162
+ values
2163
+ );
2164
+ } else {
2165
+ await this.client.none(
2166
+ `INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})
2167
+ ON CONFLICT ("traceId", "spanId") DO NOTHING`,
2168
+ values
2169
+ );
2170
+ }
2171
+ } else {
2172
+ await this.client.none(`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})`, values);
2173
+ }
1839
2174
  } catch (error) {
1840
2175
  throw new MastraError(
1841
2176
  {
@@ -1859,7 +2194,7 @@ var PgDB = class extends MastraBase {
1859
2194
  SELECT 1 FROM information_schema.tables
1860
2195
  WHERE table_schema = $1 AND table_name = $2
1861
2196
  )`,
1862
- [this.schemaName || "mastra", tableName]
2197
+ [this.schemaName || "public", tableName]
1863
2198
  );
1864
2199
  if (tableExists?.exists) {
1865
2200
  await this.client.none(`TRUNCATE TABLE ${tableNameWithSchema} CASCADE`);
@@ -1884,52 +2219,10 @@ var PgDB = class extends MastraBase {
1884
2219
  }) {
1885
2220
  try {
1886
2221
  const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
1887
- const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
1888
- const parsedName = parseSqlIdentifier(name, "column name");
1889
- return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
1890
- });
1891
- const columns = Object.entries(schema).map(([name, def]) => {
1892
- const parsedName = parseSqlIdentifier(name, "column name");
1893
- const constraints = [];
1894
- if (def.primaryKey) constraints.push("PRIMARY KEY");
1895
- if (!def.nullable) constraints.push("NOT NULL");
1896
- return `"${parsedName}" ${this.getSqlType(def.type)} ${constraints.join(" ")}`;
1897
- });
1898
2222
  if (this.schemaName) {
1899
2223
  await this.setupSchema();
1900
2224
  }
1901
- const finalColumns = [...columns, ...timeZColumns].join(",\n");
1902
- const constraintPrefix = this.schemaName ? `${this.schemaName}_` : "";
1903
- const schemaName = getSchemaName(this.schemaName);
1904
- const sql = `
1905
- CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName })} (
1906
- ${finalColumns}
1907
- );
1908
- ${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
1909
- DO $$ BEGIN
1910
- IF NOT EXISTS (
1911
- SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1912
- ) AND NOT EXISTS (
1913
- SELECT 1 FROM pg_indexes WHERE indexname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1914
- ) THEN
1915
- ALTER TABLE ${getTableName({ indexName: tableName, schemaName })}
1916
- ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
1917
- UNIQUE (workflow_name, run_id);
1918
- END IF;
1919
- END $$;
1920
- ` : ""}
1921
- ${tableName === TABLE_SPANS ? `
1922
- DO $$ BEGIN
1923
- IF NOT EXISTS (
1924
- SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_ai_spans_traceid_spanid_pk'
1925
- ) THEN
1926
- ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })}
1927
- ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
1928
- PRIMARY KEY ("traceId", "spanId");
1929
- END IF;
1930
- END $$;
1931
- ` : ""}
1932
- `;
2225
+ const sql = generateTableSQL({ tableName, schema, schemaName: this.schemaName });
1933
2226
  await this.client.none(sql);
1934
2227
  await this.alterTable({
1935
2228
  tableName,
@@ -1939,8 +2232,46 @@ var PgDB = class extends MastraBase {
1939
2232
  if (tableName === TABLE_SPANS) {
1940
2233
  await this.setupTimestampTriggers(tableName);
1941
2234
  await this.migrateSpansTable();
2235
+ const pkExists = await this.spansPrimaryKeyExists();
2236
+ if (!pkExists) {
2237
+ const duplicateInfo = await this.checkForDuplicateSpans();
2238
+ if (duplicateInfo.hasDuplicates) {
2239
+ const errorMessage = `
2240
+ ===========================================================================
2241
+ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}
2242
+ ===========================================================================
2243
+
2244
+ Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
2245
+
2246
+ The spans table requires a unique constraint on (traceId, spanId), but your
2247
+ database contains duplicate entries that must be resolved first.
2248
+
2249
+ To fix this, run the manual migration command:
2250
+
2251
+ npx mastra migrate
2252
+
2253
+ This command will:
2254
+ 1. Remove duplicate spans (keeping the most complete/recent version)
2255
+ 2. Add the required unique constraint
2256
+
2257
+ Note: This migration may take some time for large tables.
2258
+ ===========================================================================
2259
+ `;
2260
+ throw new MastraError({
2261
+ id: createStorageErrorId("PG", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
2262
+ domain: ErrorDomain.STORAGE,
2263
+ category: ErrorCategory.USER,
2264
+ text: errorMessage
2265
+ });
2266
+ } else {
2267
+ await this.addSpansPrimaryKey();
2268
+ }
2269
+ }
1942
2270
  }
1943
2271
  } catch (error) {
2272
+ if (error instanceof MastraError) {
2273
+ throw error;
2274
+ }
1944
2275
  throw new MastraError(
1945
2276
  {
1946
2277
  id: createStorageErrorId("PG", "CREATE_TABLE", "FAILED"),
@@ -2003,12 +2334,29 @@ var PgDB = class extends MastraBase {
2003
2334
  const columnExists = await this.hasColumn(TABLE_SPANS, columnName);
2004
2335
  if (!columnExists) {
2005
2336
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
2006
- const sqlType = this.getSqlType(columnDef.type);
2337
+ const sqlType = mapToSqlType(columnDef.type);
2007
2338
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2008
2339
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2009
2340
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
2010
2341
  await this.client.none(alterSql);
2011
2342
  this.logger?.debug?.(`Added column '${columnName}' to ${fullTableName}`);
2343
+ if (sqlType === "TIMESTAMP") {
2344
+ const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ DEFAULT NOW()`.trim();
2345
+ await this.client.none(timestampZSql);
2346
+ this.logger?.debug?.(`Added timezone column '${columnName}Z' to ${fullTableName}`);
2347
+ }
2348
+ }
2349
+ }
2350
+ for (const [columnName, columnDef] of Object.entries(schema)) {
2351
+ if (columnDef.type === "timestamp") {
2352
+ const tzColumnName = `${columnName}Z`;
2353
+ const tzColumnExists = await this.hasColumn(TABLE_SPANS, tzColumnName);
2354
+ if (!tzColumnExists) {
2355
+ const parsedTzColumnName = parseSqlIdentifier(tzColumnName, "column name");
2356
+ const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedTzColumnName}" TIMESTAMPTZ DEFAULT NOW()`.trim();
2357
+ await this.client.none(timestampZSql);
2358
+ this.logger?.debug?.(`Added timezone column '${tzColumnName}' to ${fullTableName}`);
2359
+ }
2012
2360
  }
2013
2361
  }
2014
2362
  this.logger?.info?.(`Migration completed for ${fullTableName}`);
@@ -2016,6 +2364,224 @@ var PgDB = class extends MastraBase {
2016
2364
  this.logger?.warn?.(`Failed to migrate spans table ${fullTableName}:`, error);
2017
2365
  }
2018
2366
  }
2367
+ /**
2368
+ * Deduplicates spans in the mastra_ai_spans table before adding the PRIMARY KEY constraint.
2369
+ * Keeps spans based on priority: completed (endedAt NOT NULL) > most recent updatedAt > most recent createdAt.
2370
+ *
2371
+ * Note: This prioritizes migration completion over perfect data preservation.
2372
+ * Old trace data may be lost, which is acceptable for this use case.
2373
+ */
2374
+ async deduplicateSpans() {
2375
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
2376
+ try {
2377
+ const duplicateCheck = await this.client.oneOrNone(`
2378
+ SELECT EXISTS (
2379
+ SELECT 1
2380
+ FROM ${fullTableName}
2381
+ GROUP BY "traceId", "spanId"
2382
+ HAVING COUNT(*) > 1
2383
+ LIMIT 1
2384
+ ) as has_duplicates
2385
+ `);
2386
+ if (!duplicateCheck?.has_duplicates) {
2387
+ this.logger?.debug?.(`No duplicate spans found in ${fullTableName}`);
2388
+ return;
2389
+ }
2390
+ this.logger?.info?.(`Duplicate spans detected in ${fullTableName}, starting deduplication...`);
2391
+ const result = await this.client.query(`
2392
+ DELETE FROM ${fullTableName} t1
2393
+ USING ${fullTableName} t2
2394
+ WHERE t1."traceId" = t2."traceId"
2395
+ AND t1."spanId" = t2."spanId"
2396
+ AND (
2397
+ -- Keep completed spans over incomplete
2398
+ (t1."endedAt" IS NULL AND t2."endedAt" IS NOT NULL)
2399
+ OR
2400
+ -- If both have same completion status, keep more recent updatedAt
2401
+ (
2402
+ (t1."endedAt" IS NULL) = (t2."endedAt" IS NULL)
2403
+ AND (
2404
+ (t1."updatedAt" < t2."updatedAt")
2405
+ OR (t1."updatedAt" IS NULL AND t2."updatedAt" IS NOT NULL)
2406
+ OR
2407
+ -- If updatedAt is the same, keep more recent createdAt
2408
+ (
2409
+ (t1."updatedAt" = t2."updatedAt" OR (t1."updatedAt" IS NULL AND t2."updatedAt" IS NULL))
2410
+ AND (
2411
+ (t1."createdAt" < t2."createdAt")
2412
+ OR (t1."createdAt" IS NULL AND t2."createdAt" IS NOT NULL)
2413
+ OR
2414
+ -- If all else equal, use ctid as tiebreaker
2415
+ (
2416
+ (t1."createdAt" = t2."createdAt" OR (t1."createdAt" IS NULL AND t2."createdAt" IS NULL))
2417
+ AND t1.ctid < t2.ctid
2418
+ )
2419
+ )
2420
+ )
2421
+ )
2422
+ )
2423
+ )
2424
+ `);
2425
+ this.logger?.info?.(
2426
+ `Deduplication complete: removed ${result.rowCount ?? 0} duplicate spans from ${fullTableName}`
2427
+ );
2428
+ } catch (error) {
2429
+ throw new MastraError(
2430
+ {
2431
+ id: createStorageErrorId("PG", "DEDUPLICATE_SPANS", "FAILED"),
2432
+ domain: ErrorDomain.STORAGE,
2433
+ category: ErrorCategory.THIRD_PARTY,
2434
+ details: {
2435
+ tableName: TABLE_SPANS
2436
+ }
2437
+ },
2438
+ error
2439
+ );
2440
+ }
2441
+ }
2442
+ /**
2443
+ * Checks for duplicate (traceId, spanId) combinations in the spans table.
2444
+ * Returns information about duplicates for logging/CLI purposes.
2445
+ */
2446
+ async checkForDuplicateSpans() {
2447
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
2448
+ try {
2449
+ const result = await this.client.oneOrNone(`
2450
+ SELECT COUNT(*) as duplicate_count
2451
+ FROM (
2452
+ SELECT "traceId", "spanId"
2453
+ FROM ${fullTableName}
2454
+ GROUP BY "traceId", "spanId"
2455
+ HAVING COUNT(*) > 1
2456
+ ) duplicates
2457
+ `);
2458
+ const duplicateCount = parseInt(result?.duplicate_count ?? "0", 10);
2459
+ return {
2460
+ hasDuplicates: duplicateCount > 0,
2461
+ duplicateCount,
2462
+ tableName: fullTableName
2463
+ };
2464
+ } catch (error) {
2465
+ this.logger?.debug?.(`Could not check for duplicates: ${error}`);
2466
+ return { hasDuplicates: false, duplicateCount: 0, tableName: fullTableName };
2467
+ }
2468
+ }
2469
+ /**
2470
+ * Checks if the PRIMARY KEY constraint on (traceId, spanId) already exists on the spans table.
2471
+ * Used to skip deduplication when the constraint already exists (migration already complete).
2472
+ */
2473
+ async spansPrimaryKeyExists() {
2474
+ const parsedSchemaName = this.schemaName ? parseSqlIdentifier(this.schemaName, "schema name") : "";
2475
+ const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
2476
+ const constraintName = `${constraintPrefix}mastra_ai_spans_traceid_spanid_pk`;
2477
+ const result = await this.client.oneOrNone(
2478
+ `SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = $1) as exists`,
2479
+ [constraintName]
2480
+ );
2481
+ return result?.exists ?? false;
2482
+ }
2483
+ /**
2484
+ * Adds the PRIMARY KEY constraint on (traceId, spanId) to the spans table.
2485
+ * Should be called AFTER deduplication to ensure no duplicate key violations.
2486
+ */
2487
+ async addSpansPrimaryKey() {
2488
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
2489
+ const parsedSchemaName = this.schemaName ? parseSqlIdentifier(this.schemaName, "schema name") : "";
2490
+ const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
2491
+ const constraintName = `${constraintPrefix}mastra_ai_spans_traceid_spanid_pk`;
2492
+ try {
2493
+ const constraintExists = await this.client.oneOrNone(
2494
+ `
2495
+ SELECT EXISTS (
2496
+ SELECT 1 FROM pg_constraint WHERE conname = $1
2497
+ ) as exists
2498
+ `,
2499
+ [constraintName]
2500
+ );
2501
+ if (constraintExists?.exists) {
2502
+ this.logger?.debug?.(`PRIMARY KEY constraint ${constraintName} already exists on ${fullTableName}`);
2503
+ return;
2504
+ }
2505
+ await this.client.none(`
2506
+ ALTER TABLE ${fullTableName}
2507
+ ADD CONSTRAINT ${constraintName}
2508
+ PRIMARY KEY ("traceId", "spanId")
2509
+ `);
2510
+ this.logger?.info?.(`Added PRIMARY KEY constraint ${constraintName} to ${fullTableName}`);
2511
+ } catch (error) {
2512
+ throw new MastraError(
2513
+ {
2514
+ id: createStorageErrorId("PG", "ADD_SPANS_PRIMARY_KEY", "FAILED"),
2515
+ domain: ErrorDomain.STORAGE,
2516
+ category: ErrorCategory.THIRD_PARTY,
2517
+ details: {
2518
+ tableName: TABLE_SPANS,
2519
+ constraintName
2520
+ }
2521
+ },
2522
+ error
2523
+ );
2524
+ }
2525
+ }
2526
+ /**
2527
+ * Manually run the spans migration to deduplicate and add the unique constraint.
2528
+ * This is intended to be called from the CLI when duplicates are detected.
2529
+ *
2530
+ * @returns Migration result with status and details
2531
+ */
2532
+ async migrateSpans() {
2533
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
2534
+ const pkExists = await this.spansPrimaryKeyExists();
2535
+ if (pkExists) {
2536
+ return {
2537
+ success: true,
2538
+ alreadyMigrated: true,
2539
+ duplicatesRemoved: 0,
2540
+ message: `Migration already complete. PRIMARY KEY constraint exists on ${fullTableName}.`
2541
+ };
2542
+ }
2543
+ const duplicateInfo = await this.checkForDuplicateSpans();
2544
+ if (duplicateInfo.hasDuplicates) {
2545
+ this.logger?.info?.(
2546
+ `Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
2547
+ );
2548
+ await this.deduplicateSpans();
2549
+ } else {
2550
+ this.logger?.info?.(`No duplicate spans found.`);
2551
+ }
2552
+ await this.addSpansPrimaryKey();
2553
+ return {
2554
+ success: true,
2555
+ alreadyMigrated: false,
2556
+ duplicatesRemoved: duplicateInfo.duplicateCount,
2557
+ message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added PRIMARY KEY constraint to ${fullTableName}.` : `Migration complete. Added PRIMARY KEY constraint to ${fullTableName}.`
2558
+ };
2559
+ }
2560
+ /**
2561
+ * Check migration status for the spans table.
2562
+ * Returns information about whether migration is needed.
2563
+ */
2564
+ async checkSpansMigrationStatus() {
2565
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
2566
+ const pkExists = await this.spansPrimaryKeyExists();
2567
+ if (pkExists) {
2568
+ return {
2569
+ needsMigration: false,
2570
+ hasDuplicates: false,
2571
+ duplicateCount: 0,
2572
+ constraintExists: true,
2573
+ tableName: fullTableName
2574
+ };
2575
+ }
2576
+ const duplicateInfo = await this.checkForDuplicateSpans();
2577
+ return {
2578
+ needsMigration: true,
2579
+ hasDuplicates: duplicateInfo.hasDuplicates,
2580
+ duplicateCount: duplicateInfo.duplicateCount,
2581
+ constraintExists: false,
2582
+ tableName: fullTableName
2583
+ };
2584
+ }
2019
2585
  /**
2020
2586
  * Alters table schema to add columns if they don't exist
2021
2587
  * @param tableName Name of the table
@@ -2033,7 +2599,7 @@ var PgDB = class extends MastraBase {
2033
2599
  if (schema[columnName]) {
2034
2600
  const columnDef = schema[columnName];
2035
2601
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
2036
- const sqlType = this.getSqlType(columnDef.type);
2602
+ const sqlType = mapToSqlType(columnDef.type);
2037
2603
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2038
2604
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2039
2605
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
@@ -2353,9 +2919,9 @@ var PgDB = class extends MastraBase {
2353
2919
  size: result.size || "0",
2354
2920
  definition: result.definition || "",
2355
2921
  method: result.method || "btree",
2356
- scans: parseInt(result.scans) || 0,
2357
- tuples_read: parseInt(result.tuples_read) || 0,
2358
- tuples_fetched: parseInt(result.tuples_fetched) || 0
2922
+ scans: parseInt(String(result.scans)) || 0,
2923
+ tuples_read: parseInt(String(result.tuples_read)) || 0,
2924
+ tuples_fetched: parseInt(String(result.tuples_fetched)) || 0
2359
2925
  };
2360
2926
  } catch (error) {
2361
2927
  throw new MastraError(
@@ -2527,7 +3093,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2527
3093
  #skipDefaultIndexes;
2528
3094
  #indexes;
2529
3095
  /** Tables managed by this domain */
2530
- static MANAGED_TABLES = [TABLE_AGENTS];
3096
+ static MANAGED_TABLES = [TABLE_AGENTS, TABLE_AGENT_VERSIONS];
2531
3097
  constructor(config) {
2532
3098
  super();
2533
3099
  const { client, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
@@ -2554,6 +3120,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2554
3120
  }
2555
3121
  async init() {
2556
3122
  await this.#db.createTable({ tableName: TABLE_AGENTS, schema: TABLE_SCHEMAS[TABLE_AGENTS] });
3123
+ await this.#db.createTable({ tableName: TABLE_AGENT_VERSIONS, schema: TABLE_SCHEMAS[TABLE_AGENT_VERSIONS] });
2557
3124
  await this.createDefaultIndexes();
2558
3125
  await this.createCustomIndexes();
2559
3126
  }
@@ -2573,6 +3140,7 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2573
3140
  }
2574
3141
  }
2575
3142
  async dangerouslyClearAll() {
3143
+ await this.#db.clearTable({ tableName: TABLE_AGENT_VERSIONS });
2576
3144
  await this.#db.clearTable({ tableName: TABLE_AGENTS });
2577
3145
  }
2578
3146
  parseJson(value, fieldName) {
@@ -2610,11 +3178,14 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2610
3178
  defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
2611
3179
  workflows: this.parseJson(row.workflows, "workflows"),
2612
3180
  agents: this.parseJson(row.agents, "agents"),
3181
+ integrationTools: this.parseJson(row.integrationTools, "integrationTools"),
2613
3182
  inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
2614
3183
  outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
2615
3184
  memory: this.parseJson(row.memory, "memory"),
2616
3185
  scorers: this.parseJson(row.scorers, "scorers"),
2617
3186
  metadata: this.parseJson(row.metadata, "metadata"),
3187
+ ownerId: row.ownerId,
3188
+ activeVersionId: row.activeVersionId,
2618
3189
  createdAt: row.createdAtZ || row.createdAt,
2619
3190
  updatedAt: row.updatedAtZ || row.updatedAt
2620
3191
  };
@@ -2646,10 +3217,12 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2646
3217
  const nowIso = now.toISOString();
2647
3218
  await this.#db.client.none(
2648
3219
  `INSERT INTO ${tableName} (
2649
- id, name, description, instructions, model, tools,
2650
- "defaultOptions", workflows, agents, "inputProcessors", "outputProcessors", memory, scorers, metadata,
3220
+ id, name, description, instructions, model, tools,
3221
+ "defaultOptions", workflows, agents, "integrationTools",
3222
+ "inputProcessors", "outputProcessors", memory, scorers, metadata,
3223
+ "ownerId", "activeVersionId",
2651
3224
  "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
2652
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)`,
3225
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)`,
2653
3226
  [
2654
3227
  agent.id,
2655
3228
  agent.name,
@@ -2660,11 +3233,14 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2660
3233
  agent.defaultOptions ? JSON.stringify(agent.defaultOptions) : null,
2661
3234
  agent.workflows ? JSON.stringify(agent.workflows) : null,
2662
3235
  agent.agents ? JSON.stringify(agent.agents) : null,
3236
+ agent.integrationTools ? JSON.stringify(agent.integrationTools) : null,
2663
3237
  agent.inputProcessors ? JSON.stringify(agent.inputProcessors) : null,
2664
3238
  agent.outputProcessors ? JSON.stringify(agent.outputProcessors) : null,
2665
3239
  agent.memory ? JSON.stringify(agent.memory) : null,
2666
3240
  agent.scorers ? JSON.stringify(agent.scorers) : null,
2667
3241
  agent.metadata ? JSON.stringify(agent.metadata) : null,
3242
+ agent.ownerId ?? null,
3243
+ agent.activeVersionId ?? null,
2668
3244
  nowIso,
2669
3245
  nowIso,
2670
3246
  nowIso,
@@ -2752,6 +3328,18 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2752
3328
  setClauses.push(`scorers = $${paramIndex++}`);
2753
3329
  values.push(JSON.stringify(updates.scorers));
2754
3330
  }
3331
+ if (updates.integrationTools !== void 0) {
3332
+ setClauses.push(`"integrationTools" = $${paramIndex++}`);
3333
+ values.push(JSON.stringify(updates.integrationTools));
3334
+ }
3335
+ if (updates.ownerId !== void 0) {
3336
+ setClauses.push(`"ownerId" = $${paramIndex++}`);
3337
+ values.push(updates.ownerId);
3338
+ }
3339
+ if (updates.activeVersionId !== void 0) {
3340
+ setClauses.push(`"activeVersionId" = $${paramIndex++}`);
3341
+ values.push(updates.activeVersionId);
3342
+ }
2755
3343
  if (updates.metadata !== void 0) {
2756
3344
  const mergedMetadata = { ...existingAgent.metadata, ...updates.metadata };
2757
3345
  setClauses.push(`metadata = $${paramIndex++}`);
@@ -2774,50 +3362,209 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2774
3362
  throw new MastraError({
2775
3363
  id: createStorageErrorId("PG", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
2776
3364
  domain: ErrorDomain.STORAGE,
2777
- category: ErrorCategory.SYSTEM,
2778
- text: `Agent ${id} not found after update`,
2779
- details: { agentId: id }
2780
- });
3365
+ category: ErrorCategory.SYSTEM,
3366
+ text: `Agent ${id} not found after update`,
3367
+ details: { agentId: id }
3368
+ });
3369
+ }
3370
+ return updatedAgent;
3371
+ } catch (error) {
3372
+ if (error instanceof MastraError) {
3373
+ throw error;
3374
+ }
3375
+ throw new MastraError(
3376
+ {
3377
+ id: createStorageErrorId("PG", "UPDATE_AGENT", "FAILED"),
3378
+ domain: ErrorDomain.STORAGE,
3379
+ category: ErrorCategory.THIRD_PARTY,
3380
+ details: { agentId: id }
3381
+ },
3382
+ error
3383
+ );
3384
+ }
3385
+ }
3386
+ async deleteAgent({ id }) {
3387
+ try {
3388
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
3389
+ await this.deleteVersionsByAgentId(id);
3390
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
3391
+ } catch (error) {
3392
+ throw new MastraError(
3393
+ {
3394
+ id: createStorageErrorId("PG", "DELETE_AGENT", "FAILED"),
3395
+ domain: ErrorDomain.STORAGE,
3396
+ category: ErrorCategory.THIRD_PARTY,
3397
+ details: { agentId: id }
3398
+ },
3399
+ error
3400
+ );
3401
+ }
3402
+ }
3403
+ async listAgents(args) {
3404
+ const { page = 0, perPage: perPageInput, orderBy } = args || {};
3405
+ const { field, direction } = this.parseOrderBy(orderBy);
3406
+ if (page < 0) {
3407
+ throw new MastraError(
3408
+ {
3409
+ id: createStorageErrorId("PG", "LIST_AGENTS", "INVALID_PAGE"),
3410
+ domain: ErrorDomain.STORAGE,
3411
+ category: ErrorCategory.USER,
3412
+ details: { page }
3413
+ },
3414
+ new Error("page must be >= 0")
3415
+ );
3416
+ }
3417
+ const perPage = normalizePerPage(perPageInput, 100);
3418
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3419
+ try {
3420
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
3421
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName}`);
3422
+ const total = parseInt(countResult.count, 10);
3423
+ if (total === 0) {
3424
+ return {
3425
+ agents: [],
3426
+ total: 0,
3427
+ page,
3428
+ perPage: perPageForResponse,
3429
+ hasMore: false
3430
+ };
3431
+ }
3432
+ const limitValue = perPageInput === false ? total : perPage;
3433
+ const dataResult = await this.#db.client.manyOrNone(
3434
+ `SELECT * FROM ${tableName} ORDER BY "${field}" ${direction} LIMIT $1 OFFSET $2`,
3435
+ [limitValue, offset]
3436
+ );
3437
+ const agents = (dataResult || []).map((row) => this.parseRow(row));
3438
+ return {
3439
+ agents,
3440
+ total,
3441
+ page,
3442
+ perPage: perPageForResponse,
3443
+ hasMore: perPageInput === false ? false : offset + perPage < total
3444
+ };
3445
+ } catch (error) {
3446
+ throw new MastraError(
3447
+ {
3448
+ id: createStorageErrorId("PG", "LIST_AGENTS", "FAILED"),
3449
+ domain: ErrorDomain.STORAGE,
3450
+ category: ErrorCategory.THIRD_PARTY
3451
+ },
3452
+ error
3453
+ );
3454
+ }
3455
+ }
3456
+ // ==========================================================================
3457
+ // Agent Version Methods
3458
+ // ==========================================================================
3459
+ async createVersion(input) {
3460
+ try {
3461
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3462
+ const now = /* @__PURE__ */ new Date();
3463
+ const nowIso = now.toISOString();
3464
+ await this.#db.client.none(
3465
+ `INSERT INTO ${tableName} (
3466
+ id, "agentId", "versionNumber", name, snapshot, "changedFields", "changeMessage", "createdAt", "createdAtZ"
3467
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
3468
+ [
3469
+ input.id,
3470
+ input.agentId,
3471
+ input.versionNumber,
3472
+ input.name ?? null,
3473
+ JSON.stringify(input.snapshot),
3474
+ input.changedFields ? JSON.stringify(input.changedFields) : null,
3475
+ input.changeMessage ?? null,
3476
+ nowIso,
3477
+ nowIso
3478
+ ]
3479
+ );
3480
+ return {
3481
+ ...input,
3482
+ createdAt: now
3483
+ };
3484
+ } catch (error) {
3485
+ throw new MastraError(
3486
+ {
3487
+ id: createStorageErrorId("PG", "CREATE_VERSION", "FAILED"),
3488
+ domain: ErrorDomain.STORAGE,
3489
+ category: ErrorCategory.THIRD_PARTY,
3490
+ details: { versionId: input.id, agentId: input.agentId }
3491
+ },
3492
+ error
3493
+ );
3494
+ }
3495
+ }
3496
+ async getVersion(id) {
3497
+ try {
3498
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3499
+ const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
3500
+ if (!result) {
3501
+ return null;
2781
3502
  }
2782
- return updatedAgent;
3503
+ return this.parseVersionRow(result);
2783
3504
  } catch (error) {
2784
- if (error instanceof MastraError) {
2785
- throw error;
3505
+ throw new MastraError(
3506
+ {
3507
+ id: createStorageErrorId("PG", "GET_VERSION", "FAILED"),
3508
+ domain: ErrorDomain.STORAGE,
3509
+ category: ErrorCategory.THIRD_PARTY,
3510
+ details: { versionId: id }
3511
+ },
3512
+ error
3513
+ );
3514
+ }
3515
+ }
3516
+ async getVersionByNumber(agentId, versionNumber) {
3517
+ try {
3518
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3519
+ const result = await this.#db.client.oneOrNone(
3520
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 AND "versionNumber" = $2`,
3521
+ [agentId, versionNumber]
3522
+ );
3523
+ if (!result) {
3524
+ return null;
2786
3525
  }
3526
+ return this.parseVersionRow(result);
3527
+ } catch (error) {
2787
3528
  throw new MastraError(
2788
3529
  {
2789
- id: createStorageErrorId("PG", "UPDATE_AGENT", "FAILED"),
3530
+ id: createStorageErrorId("PG", "GET_VERSION_BY_NUMBER", "FAILED"),
2790
3531
  domain: ErrorDomain.STORAGE,
2791
3532
  category: ErrorCategory.THIRD_PARTY,
2792
- details: { agentId: id }
3533
+ details: { agentId, versionNumber }
2793
3534
  },
2794
3535
  error
2795
3536
  );
2796
3537
  }
2797
3538
  }
2798
- async deleteAgent({ id }) {
3539
+ async getLatestVersion(agentId) {
2799
3540
  try {
2800
- const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
2801
- await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
3541
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3542
+ const result = await this.#db.client.oneOrNone(
3543
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "versionNumber" DESC LIMIT 1`,
3544
+ [agentId]
3545
+ );
3546
+ if (!result) {
3547
+ return null;
3548
+ }
3549
+ return this.parseVersionRow(result);
2802
3550
  } catch (error) {
2803
3551
  throw new MastraError(
2804
3552
  {
2805
- id: createStorageErrorId("PG", "DELETE_AGENT", "FAILED"),
3553
+ id: createStorageErrorId("PG", "GET_LATEST_VERSION", "FAILED"),
2806
3554
  domain: ErrorDomain.STORAGE,
2807
3555
  category: ErrorCategory.THIRD_PARTY,
2808
- details: { agentId: id }
3556
+ details: { agentId }
2809
3557
  },
2810
3558
  error
2811
3559
  );
2812
3560
  }
2813
3561
  }
2814
- async listAgents(args) {
2815
- const { page = 0, perPage: perPageInput, orderBy } = args || {};
2816
- const { field, direction } = this.parseOrderBy(orderBy);
3562
+ async listVersions(input) {
3563
+ const { agentId, page = 0, perPage: perPageInput, orderBy } = input;
2817
3564
  if (page < 0) {
2818
3565
  throw new MastraError(
2819
3566
  {
2820
- id: createStorageErrorId("PG", "LIST_AGENTS", "INVALID_PAGE"),
3567
+ id: createStorageErrorId("PG", "LIST_VERSIONS", "INVALID_PAGE"),
2821
3568
  domain: ErrorDomain.STORAGE,
2822
3569
  category: ErrorCategory.USER,
2823
3570
  details: { page }
@@ -2825,15 +3572,18 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2825
3572
  new Error("page must be >= 0")
2826
3573
  );
2827
3574
  }
2828
- const perPage = normalizePerPage(perPageInput, 100);
3575
+ const perPage = normalizePerPage(perPageInput, 20);
2829
3576
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2830
3577
  try {
2831
- const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
2832
- const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName}`);
3578
+ const { field, direction } = this.parseVersionOrderBy(orderBy);
3579
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3580
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
3581
+ agentId
3582
+ ]);
2833
3583
  const total = parseInt(countResult.count, 10);
2834
3584
  if (total === 0) {
2835
3585
  return {
2836
- agents: [],
3586
+ versions: [],
2837
3587
  total: 0,
2838
3588
  page,
2839
3589
  perPage: perPageForResponse,
@@ -2842,12 +3592,12 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2842
3592
  }
2843
3593
  const limitValue = perPageInput === false ? total : perPage;
2844
3594
  const dataResult = await this.#db.client.manyOrNone(
2845
- `SELECT * FROM ${tableName} ORDER BY "${field}" ${direction} LIMIT $1 OFFSET $2`,
2846
- [limitValue, offset]
3595
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`,
3596
+ [agentId, limitValue, offset]
2847
3597
  );
2848
- const agents = (dataResult || []).map((row) => this.parseRow(row));
3598
+ const versions = (dataResult || []).map((row) => this.parseVersionRow(row));
2849
3599
  return {
2850
- agents,
3600
+ versions,
2851
3601
  total,
2852
3602
  page,
2853
3603
  perPage: perPageForResponse,
@@ -2856,14 +3606,81 @@ var AgentsPG = class _AgentsPG extends AgentsStorage {
2856
3606
  } catch (error) {
2857
3607
  throw new MastraError(
2858
3608
  {
2859
- id: createStorageErrorId("PG", "LIST_AGENTS", "FAILED"),
3609
+ id: createStorageErrorId("PG", "LIST_VERSIONS", "FAILED"),
2860
3610
  domain: ErrorDomain.STORAGE,
2861
- category: ErrorCategory.THIRD_PARTY
3611
+ category: ErrorCategory.THIRD_PARTY,
3612
+ details: { agentId }
3613
+ },
3614
+ error
3615
+ );
3616
+ }
3617
+ }
3618
+ async deleteVersion(id) {
3619
+ try {
3620
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3621
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
3622
+ } catch (error) {
3623
+ throw new MastraError(
3624
+ {
3625
+ id: createStorageErrorId("PG", "DELETE_VERSION", "FAILED"),
3626
+ domain: ErrorDomain.STORAGE,
3627
+ category: ErrorCategory.THIRD_PARTY,
3628
+ details: { versionId: id }
3629
+ },
3630
+ error
3631
+ );
3632
+ }
3633
+ }
3634
+ async deleteVersionsByAgentId(agentId) {
3635
+ try {
3636
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3637
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE "agentId" = $1`, [agentId]);
3638
+ } catch (error) {
3639
+ throw new MastraError(
3640
+ {
3641
+ id: createStorageErrorId("PG", "DELETE_VERSIONS_BY_AGENT_ID", "FAILED"),
3642
+ domain: ErrorDomain.STORAGE,
3643
+ category: ErrorCategory.THIRD_PARTY,
3644
+ details: { agentId }
3645
+ },
3646
+ error
3647
+ );
3648
+ }
3649
+ }
3650
+ async countVersions(agentId) {
3651
+ try {
3652
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
3653
+ const result = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
3654
+ agentId
3655
+ ]);
3656
+ return parseInt(result.count, 10);
3657
+ } catch (error) {
3658
+ throw new MastraError(
3659
+ {
3660
+ id: createStorageErrorId("PG", "COUNT_VERSIONS", "FAILED"),
3661
+ domain: ErrorDomain.STORAGE,
3662
+ category: ErrorCategory.THIRD_PARTY,
3663
+ details: { agentId }
2862
3664
  },
2863
3665
  error
2864
3666
  );
2865
3667
  }
2866
3668
  }
3669
+ // ==========================================================================
3670
+ // Private Helper Methods
3671
+ // ==========================================================================
3672
+ parseVersionRow(row) {
3673
+ return {
3674
+ id: row.id,
3675
+ agentId: row.agentId,
3676
+ versionNumber: row.versionNumber,
3677
+ name: row.name,
3678
+ snapshot: this.parseJson(row.snapshot, "snapshot"),
3679
+ changedFields: this.parseJson(row.changedFields, "changedFields"),
3680
+ changeMessage: row.changeMessage,
3681
+ createdAt: row.createdAtZ || row.createdAt
3682
+ };
3683
+ }
2867
3684
  };
2868
3685
  function getSchemaName3(schema) {
2869
3686
  return schema ? `"${schema}"` : '"public"';
@@ -2872,6 +3689,9 @@ function getTableName3({ indexName, schemaName }) {
2872
3689
  const quotedIndexName = `"${indexName}"`;
2873
3690
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
2874
3691
  }
3692
+ function inPlaceholders(count, startIndex = 1) {
3693
+ return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
3694
+ }
2875
3695
  var MemoryPG = class _MemoryPG extends MemoryStorage {
2876
3696
  #db;
2877
3697
  #schema;
@@ -2998,27 +3818,52 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
2998
3818
  );
2999
3819
  }
3000
3820
  }
3001
- async listThreadsByResourceId(args) {
3002
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
3003
- if (page < 0) {
3821
+ async listThreads(args) {
3822
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
3823
+ try {
3824
+ this.validatePaginationInput(page, perPageInput ?? 100);
3825
+ } catch (error) {
3004
3826
  throw new MastraError({
3005
- id: createStorageErrorId("PG", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
3827
+ id: createStorageErrorId("PG", "LIST_THREADS", "INVALID_PAGE"),
3006
3828
  domain: ErrorDomain.STORAGE,
3007
3829
  category: ErrorCategory.USER,
3008
- text: "Page number must be non-negative",
3009
- details: {
3010
- resourceId,
3011
- page
3012
- }
3830
+ text: error instanceof Error ? error.message : "Invalid pagination parameters",
3831
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
3013
3832
  });
3014
3833
  }
3015
- const { field, direction } = this.parseOrderBy(orderBy);
3016
3834
  const perPage = normalizePerPage(perPageInput, 100);
3835
+ try {
3836
+ this.validateMetadataKeys(filter?.metadata);
3837
+ } catch (error) {
3838
+ throw new MastraError({
3839
+ id: createStorageErrorId("PG", "LIST_THREADS", "INVALID_METADATA_KEY"),
3840
+ domain: ErrorDomain.STORAGE,
3841
+ category: ErrorCategory.USER,
3842
+ text: error instanceof Error ? error.message : "Invalid metadata key",
3843
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
3844
+ });
3845
+ }
3846
+ const { field, direction } = this.parseOrderBy(orderBy);
3017
3847
  const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3018
3848
  try {
3019
3849
  const tableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
3020
- const baseQuery = `FROM ${tableName} WHERE "resourceId" = $1`;
3021
- const queryParams = [resourceId];
3850
+ const whereClauses = [];
3851
+ const queryParams = [];
3852
+ let paramIndex = 1;
3853
+ if (filter?.resourceId) {
3854
+ whereClauses.push(`"resourceId" = $${paramIndex}`);
3855
+ queryParams.push(filter.resourceId);
3856
+ paramIndex++;
3857
+ }
3858
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
3859
+ for (const [key, value] of Object.entries(filter.metadata)) {
3860
+ whereClauses.push(`metadata::jsonb @> $${paramIndex}::jsonb`);
3861
+ queryParams.push(JSON.stringify({ [key]: value }));
3862
+ paramIndex++;
3863
+ }
3864
+ }
3865
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
3866
+ const baseQuery = `FROM ${tableName} ${whereClause}`;
3022
3867
  const countQuery = `SELECT COUNT(*) ${baseQuery}`;
3023
3868
  const countResult = await this.#db.client.one(countQuery, queryParams);
3024
3869
  const total = parseInt(countResult.count, 10);
@@ -3032,13 +3877,19 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3032
3877
  };
3033
3878
  }
3034
3879
  const limitValue = perPageInput === false ? total : perPage;
3035
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`;
3036
- const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
3880
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
3881
+ const rows = await this.#db.client.manyOrNone(
3882
+ dataQuery,
3883
+ [...queryParams, limitValue, offset]
3884
+ );
3037
3885
  const threads = (rows || []).map((thread) => ({
3038
- ...thread,
3886
+ id: thread.id,
3887
+ resourceId: thread.resourceId,
3888
+ title: thread.title,
3039
3889
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
3040
- createdAt: thread.createdAt,
3041
- updatedAt: thread.updatedAt
3890
+ // Use timezone-aware columns (*Z) for correct UTC timestamps, with fallback for legacy data
3891
+ createdAt: thread.createdAtZ || thread.createdAt,
3892
+ updatedAt: thread.updatedAtZ || thread.updatedAt
3042
3893
  }));
3043
3894
  return {
3044
3895
  threads,
@@ -3050,11 +3901,12 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3050
3901
  } catch (error) {
3051
3902
  const mastraError = new MastraError(
3052
3903
  {
3053
- id: createStorageErrorId("PG", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
3904
+ id: createStorageErrorId("PG", "LIST_THREADS", "FAILED"),
3054
3905
  domain: ErrorDomain.STORAGE,
3055
3906
  category: ErrorCategory.THIRD_PARTY,
3056
3907
  details: {
3057
- resourceId,
3908
+ ...filter?.resourceId && { resourceId: filter.resourceId },
3909
+ hasMetadataFilter: !!filter?.metadata,
3058
3910
  page
3059
3911
  }
3060
3912
  },
@@ -3143,17 +3995,18 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3143
3995
  ...metadata
3144
3996
  };
3145
3997
  try {
3998
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3146
3999
  const thread = await this.#db.client.one(
3147
4000
  `UPDATE ${threadTableName}
3148
4001
  SET
3149
4002
  title = $1,
3150
4003
  metadata = $2,
3151
4004
  "updatedAt" = $3,
3152
- "updatedAtZ" = $3
3153
- WHERE id = $4
4005
+ "updatedAtZ" = $4
4006
+ WHERE id = $5
3154
4007
  RETURNING *
3155
4008
  `,
3156
- [title, mergedMetadata, (/* @__PURE__ */ new Date()).toISOString(), id]
4009
+ [title, mergedMetadata, now, now, id]
3157
4010
  );
3158
4011
  return {
3159
4012
  id: thread.id,
@@ -3296,7 +4149,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3296
4149
  const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3297
4150
  const query = `
3298
4151
  ${selectStatement} FROM ${tableName}
3299
- WHERE id IN (${messageIds.map((_, i) => `$${i + 1}`).join(", ")})
4152
+ WHERE id IN (${inPlaceholders(messageIds.length)})
3300
4153
  ORDER BY "createdAt" DESC
3301
4154
  `;
3302
4155
  const resultRows = await this.#db.client.manyOrNone(query, messageIds);
@@ -3357,8 +4210,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3357
4210
  const orderByStatement = `ORDER BY "${field}" ${direction}`;
3358
4211
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
3359
4212
  const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3360
- const threadPlaceholders = threadIds.map((_, i) => `$${i + 1}`).join(", ");
3361
- const conditions = [`thread_id IN (${threadPlaceholders})`];
4213
+ const conditions = [`thread_id IN (${inPlaceholders(threadIds.length)})`];
3362
4214
  const queryParams = [...threadIds];
3363
4215
  let paramIndex = threadIds.length + 1;
3364
4216
  if (resourceId) {
@@ -3366,11 +4218,13 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3366
4218
  queryParams.push(resourceId);
3367
4219
  }
3368
4220
  if (filter?.dateRange?.start) {
3369
- conditions.push(`"createdAt" >= $${paramIndex++}`);
4221
+ const startOp = filter.dateRange.startExclusive ? ">" : ">=";
4222
+ conditions.push(`"createdAt" ${startOp} $${paramIndex++}`);
3370
4223
  queryParams.push(filter.dateRange.start);
3371
4224
  }
3372
4225
  if (filter?.dateRange?.end) {
3373
- conditions.push(`"createdAt" <= $${paramIndex++}`);
4226
+ const endOp = filter.dateRange.endExclusive ? "<" : "<=";
4227
+ conditions.push(`"createdAt" ${endOp} $${paramIndex++}`);
3374
4228
  queryParams.push(filter.dateRange.end);
3375
4229
  }
3376
4230
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
@@ -3515,14 +4369,15 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3515
4369
  );
3516
4370
  });
3517
4371
  const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
4372
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
3518
4373
  const threadUpdate = t.none(
3519
4374
  `UPDATE ${threadTableName}
3520
4375
  SET
3521
4376
  "updatedAt" = $1,
3522
- "updatedAtZ" = $1
3523
- WHERE id = $2
4377
+ "updatedAtZ" = $2
4378
+ WHERE id = $3
3524
4379
  `,
3525
- [(/* @__PURE__ */ new Date()).toISOString(), threadId]
4380
+ [nowStr, nowStr, threadId]
3526
4381
  );
3527
4382
  await Promise.all([...messageInserts, threadUpdate]);
3528
4383
  });
@@ -3559,8 +4414,8 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3559
4414
  return [];
3560
4415
  }
3561
4416
  const messageIds = messages.map((m) => m.id);
3562
- const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN ($1:list)`;
3563
- const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
4417
+ const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN (${inPlaceholders(messageIds.length)})`;
4418
+ const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, messageIds);
3564
4419
  if (existingMessagesDb.length === 0) {
3565
4420
  return [];
3566
4421
  }
@@ -3621,10 +4476,11 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3621
4476
  }
3622
4477
  }
3623
4478
  if (threadIdsToUpdate.size > 0) {
4479
+ const threadIds = Array.from(threadIdsToUpdate);
3624
4480
  queries.push(
3625
4481
  t.none(
3626
- `UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN ($1:list)`,
3627
- [Array.from(threadIdsToUpdate)]
4482
+ `UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`,
4483
+ threadIds
3628
4484
  )
3629
4485
  );
3630
4486
  }
@@ -3632,7 +4488,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3632
4488
  await t.batch(queries);
3633
4489
  }
3634
4490
  });
3635
- const updatedMessages = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
4491
+ const updatedMessages = await this.#db.client.manyOrNone(selectQuery, messageIds);
3636
4492
  return (updatedMessages || []).map((row) => {
3637
4493
  const message = this.normalizeMessageRow(row);
3638
4494
  if (typeof message.content === "string") {
@@ -3744,15 +4600,159 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3744
4600
  values.push(JSON.stringify(updatedResource.metadata));
3745
4601
  paramIndex++;
3746
4602
  }
3747
- updates.push(`"updatedAt" = $${paramIndex}`);
3748
- values.push(updatedResource.updatedAt.toISOString());
4603
+ const updatedAtStr = updatedResource.updatedAt.toISOString();
4604
+ updates.push(`"updatedAt" = $${paramIndex++}`);
4605
+ values.push(updatedAtStr);
3749
4606
  updates.push(`"updatedAtZ" = $${paramIndex++}`);
3750
- values.push(updatedResource.updatedAt.toISOString());
3751
- paramIndex++;
4607
+ values.push(updatedAtStr);
3752
4608
  values.push(resourceId);
3753
4609
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
3754
4610
  return updatedResource;
3755
4611
  }
4612
+ async cloneThread(args) {
4613
+ const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
4614
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
4615
+ if (!sourceThread) {
4616
+ throw new MastraError({
4617
+ id: createStorageErrorId("PG", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
4618
+ domain: ErrorDomain.STORAGE,
4619
+ category: ErrorCategory.USER,
4620
+ text: `Source thread with id ${sourceThreadId} not found`,
4621
+ details: { sourceThreadId }
4622
+ });
4623
+ }
4624
+ const newThreadId = providedThreadId || crypto.randomUUID();
4625
+ const existingThread = await this.getThreadById({ threadId: newThreadId });
4626
+ if (existingThread) {
4627
+ throw new MastraError({
4628
+ id: createStorageErrorId("PG", "CLONE_THREAD", "THREAD_EXISTS"),
4629
+ domain: ErrorDomain.STORAGE,
4630
+ category: ErrorCategory.USER,
4631
+ text: `Thread with id ${newThreadId} already exists`,
4632
+ details: { newThreadId }
4633
+ });
4634
+ }
4635
+ const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
4636
+ const messageTableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
4637
+ try {
4638
+ return await this.#db.client.tx(async (t) => {
4639
+ let messageQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"
4640
+ FROM ${messageTableName} WHERE thread_id = $1`;
4641
+ const messageParams = [sourceThreadId];
4642
+ let paramIndex = 2;
4643
+ if (options?.messageFilter?.startDate) {
4644
+ messageQuery += ` AND "createdAt" >= $${paramIndex++}`;
4645
+ messageParams.push(options.messageFilter.startDate);
4646
+ }
4647
+ if (options?.messageFilter?.endDate) {
4648
+ messageQuery += ` AND "createdAt" <= $${paramIndex++}`;
4649
+ messageParams.push(options.messageFilter.endDate);
4650
+ }
4651
+ if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
4652
+ messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => `$${paramIndex++}`).join(", ")})`;
4653
+ messageParams.push(...options.messageFilter.messageIds);
4654
+ }
4655
+ messageQuery += ` ORDER BY "createdAt" ASC`;
4656
+ if (options?.messageLimit && options.messageLimit > 0) {
4657
+ const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT $${paramIndex}) AS limited ORDER BY "createdAt" ASC`;
4658
+ messageParams.push(options.messageLimit);
4659
+ messageQuery = limitQuery;
4660
+ }
4661
+ const sourceMessages = await t.manyOrNone(messageQuery, messageParams);
4662
+ const now = /* @__PURE__ */ new Date();
4663
+ const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
4664
+ const cloneMetadata = {
4665
+ sourceThreadId,
4666
+ clonedAt: now,
4667
+ ...lastMessageId && { lastMessageId }
4668
+ };
4669
+ const newThread = {
4670
+ id: newThreadId,
4671
+ resourceId: resourceId || sourceThread.resourceId,
4672
+ title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
4673
+ metadata: {
4674
+ ...metadata,
4675
+ clone: cloneMetadata
4676
+ },
4677
+ createdAt: now,
4678
+ updatedAt: now
4679
+ };
4680
+ await t.none(
4681
+ `INSERT INTO ${threadTableName} (
4682
+ id,
4683
+ "resourceId",
4684
+ title,
4685
+ metadata,
4686
+ "createdAt",
4687
+ "createdAtZ",
4688
+ "updatedAt",
4689
+ "updatedAtZ"
4690
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4691
+ [
4692
+ newThread.id,
4693
+ newThread.resourceId,
4694
+ newThread.title,
4695
+ newThread.metadata ? JSON.stringify(newThread.metadata) : null,
4696
+ now,
4697
+ now,
4698
+ now,
4699
+ now
4700
+ ]
4701
+ );
4702
+ const clonedMessages = [];
4703
+ const targetResourceId = resourceId || sourceThread.resourceId;
4704
+ for (const sourceMsg of sourceMessages) {
4705
+ const newMessageId = crypto.randomUUID();
4706
+ const normalizedMsg = this.normalizeMessageRow(sourceMsg);
4707
+ let parsedContent = normalizedMsg.content;
4708
+ try {
4709
+ parsedContent = JSON.parse(normalizedMsg.content);
4710
+ } catch {
4711
+ }
4712
+ await t.none(
4713
+ `INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
4714
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4715
+ [
4716
+ newMessageId,
4717
+ newThreadId,
4718
+ typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
4719
+ normalizedMsg.createdAt,
4720
+ normalizedMsg.createdAt,
4721
+ normalizedMsg.role,
4722
+ normalizedMsg.type || "v2",
4723
+ targetResourceId
4724
+ ]
4725
+ );
4726
+ clonedMessages.push({
4727
+ id: newMessageId,
4728
+ threadId: newThreadId,
4729
+ content: parsedContent,
4730
+ role: normalizedMsg.role,
4731
+ type: normalizedMsg.type,
4732
+ createdAt: new Date(normalizedMsg.createdAt),
4733
+ resourceId: targetResourceId
4734
+ });
4735
+ }
4736
+ return {
4737
+ thread: newThread,
4738
+ clonedMessages
4739
+ };
4740
+ });
4741
+ } catch (error) {
4742
+ if (error instanceof MastraError) {
4743
+ throw error;
4744
+ }
4745
+ throw new MastraError(
4746
+ {
4747
+ id: createStorageErrorId("PG", "CLONE_THREAD", "FAILED"),
4748
+ domain: ErrorDomain.STORAGE,
4749
+ category: ErrorCategory.THIRD_PARTY,
4750
+ details: { sourceThreadId, newThreadId }
4751
+ },
4752
+ error
4753
+ );
4754
+ }
4755
+ }
3756
4756
  };
3757
4757
  var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
3758
4758
  #db;
@@ -3870,6 +4870,22 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
3870
4870
  }
3871
4871
  }
3872
4872
  }
4873
+ /**
4874
+ * Manually run the spans migration to deduplicate and add the unique constraint.
4875
+ * This is intended to be called from the CLI when duplicates are detected.
4876
+ *
4877
+ * @returns Migration result with status and details
4878
+ */
4879
+ async migrateSpans() {
4880
+ return this.#db.migrateSpans();
4881
+ }
4882
+ /**
4883
+ * Check migration status for the spans table.
4884
+ * Returns information about whether migration is needed.
4885
+ */
4886
+ async checkSpansMigrationStatus() {
4887
+ return this.#db.checkSpansMigrationStatus();
4888
+ }
3873
4889
  async dangerouslyClearAll() {
3874
4890
  await this.#db.clearTable({ tableName: TABLE_SPANS });
3875
4891
  }
@@ -4251,11 +5267,13 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
4251
5267
  perPage,
4252
5268
  hasMore: (page + 1) * perPage < count
4253
5269
  },
4254
- spans: spans.map(
4255
- (span) => transformFromSqlRow({
4256
- tableName: TABLE_SPANS,
4257
- sqlRow: span
4258
- })
5270
+ spans: toTraceSpans(
5271
+ spans.map(
5272
+ (span) => transformFromSqlRow({
5273
+ tableName: TABLE_SPANS,
5274
+ sqlRow: span
5275
+ })
5276
+ )
4259
5277
  )
4260
5278
  };
4261
5279
  } catch (error) {
@@ -4382,6 +5400,11 @@ var ScoresPG = class _ScoresPG extends ScoresStorage {
4382
5400
  }
4383
5401
  async init() {
4384
5402
  await this.#db.createTable({ tableName: TABLE_SCORERS, schema: TABLE_SCHEMAS[TABLE_SCORERS] });
5403
+ await this.#db.alterTable({
5404
+ tableName: TABLE_SCORERS,
5405
+ schema: TABLE_SCHEMAS[TABLE_SCORERS],
5406
+ ifNotExists: ["spanId", "requestContext"]
5407
+ });
4385
5408
  await this.createDefaultIndexes();
4386
5409
  await this.createCustomIndexes();
4387
5410
  }
@@ -4733,23 +5756,8 @@ function getTableName5({ indexName, schemaName }) {
4733
5756
  const quotedIndexName = `"${indexName}"`;
4734
5757
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
4735
5758
  }
4736
- function parseWorkflowRun(row) {
4737
- let parsedSnapshot = row.snapshot;
4738
- if (typeof parsedSnapshot === "string") {
4739
- try {
4740
- parsedSnapshot = JSON.parse(row.snapshot);
4741
- } catch (e) {
4742
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
4743
- }
4744
- }
4745
- return {
4746
- workflowName: row.workflow_name,
4747
- runId: row.run_id,
4748
- snapshot: parsedSnapshot,
4749
- resourceId: row.resourceId,
4750
- createdAt: new Date(row.createdAtZ || row.createdAt),
4751
- updatedAt: new Date(row.updatedAtZ || row.updatedAt)
4752
- };
5759
+ function sanitizeJsonForPg(jsonString) {
5760
+ return jsonString.replace(/\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})/g, "");
4753
5761
  }
4754
5762
  var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
4755
5763
  #db;
@@ -4766,6 +5774,24 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
4766
5774
  this.#skipDefaultIndexes = skipDefaultIndexes;
4767
5775
  this.#indexes = indexes?.filter((idx) => _WorkflowsPG.MANAGED_TABLES.includes(idx.table));
4768
5776
  }
5777
+ parseWorkflowRun(row) {
5778
+ let parsedSnapshot = row.snapshot;
5779
+ if (typeof parsedSnapshot === "string") {
5780
+ try {
5781
+ parsedSnapshot = JSON.parse(row.snapshot);
5782
+ } catch (e) {
5783
+ this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
5784
+ }
5785
+ }
5786
+ return {
5787
+ workflowName: row.workflow_name,
5788
+ runId: row.run_id,
5789
+ snapshot: parsedSnapshot,
5790
+ resourceId: row.resourceId,
5791
+ createdAt: new Date(row.createdAtZ || row.createdAt),
5792
+ updatedAt: new Date(row.updatedAtZ || row.updatedAt)
5793
+ };
5794
+ }
4769
5795
  /**
4770
5796
  * Returns default index definitions for the workflows domain tables.
4771
5797
  * Currently no default indexes are defined for workflows.
@@ -4838,12 +5864,13 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
4838
5864
  const now = /* @__PURE__ */ new Date();
4839
5865
  const createdAtValue = createdAt ? createdAt : now;
4840
5866
  const updatedAtValue = updatedAt ? updatedAt : now;
5867
+ const sanitizedSnapshot = sanitizeJsonForPg(JSON.stringify(snapshot));
4841
5868
  await this.#db.client.none(
4842
5869
  `INSERT INTO ${getTableName5({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName5(this.#schema) })} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
4843
5870
  VALUES ($1, $2, $3, $4, $5, $6)
4844
5871
  ON CONFLICT (workflow_name, run_id) DO UPDATE
4845
5872
  SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6`,
4846
- [workflowName, runId, resourceId, JSON.stringify(snapshot), createdAtValue, updatedAtValue]
5873
+ [workflowName, runId, resourceId, sanitizedSnapshot, createdAtValue, updatedAtValue]
4847
5874
  );
4848
5875
  } catch (error) {
4849
5876
  throw new MastraError(
@@ -4906,7 +5933,7 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
4906
5933
  if (!result) {
4907
5934
  return null;
4908
5935
  }
4909
- return parseWorkflowRun(result);
5936
+ return this.parseWorkflowRun(result);
4910
5937
  } catch (error) {
4911
5938
  throw new MastraError(
4912
5939
  {
@@ -4962,7 +5989,9 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
4962
5989
  paramIndex++;
4963
5990
  }
4964
5991
  if (status) {
4965
- conditions.push(`snapshot::jsonb ->> 'status' = $${paramIndex}`);
5992
+ conditions.push(
5993
+ `regexp_replace(snapshot::text, '\\\\u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})', '', 'g')::jsonb ->> 'status' = $${paramIndex}`
5994
+ );
4966
5995
  values.push(status);
4967
5996
  paramIndex++;
4968
5997
  }
@@ -5007,7 +6036,7 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
5007
6036
  const queryValues = usePagination ? [...values, normalizedPerPage, offset] : values;
5008
6037
  const result = await this.#db.client.manyOrNone(query, queryValues);
5009
6038
  const runs = (result || []).map((row) => {
5010
- return parseWorkflowRun(row);
6039
+ return this.parseWorkflowRun(row);
5011
6040
  });
5012
6041
  return { runs, total: total || runs.length };
5013
6042
  } catch (error) {
@@ -5027,9 +6056,12 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
5027
6056
  };
5028
6057
 
5029
6058
  // src/storage/index.ts
5030
- var PostgresStore = class extends MastraStorage {
6059
+ var DEFAULT_MAX_CONNECTIONS = 20;
6060
+ var DEFAULT_IDLE_TIMEOUT_MS = 3e4;
6061
+ var PostgresStore = class extends MastraCompositeStore {
6062
+ #pool;
5031
6063
  #db;
5032
- #pgp;
6064
+ #ownsPool;
5033
6065
  schema;
5034
6066
  isInitialized = false;
5035
6067
  stores;
@@ -5037,60 +6069,27 @@ var PostgresStore = class extends MastraStorage {
5037
6069
  try {
5038
6070
  validateConfig("PostgresStore", config);
5039
6071
  super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit });
5040
- this.schema = config.schemaName || "public";
5041
- this.#pgp = pgPromise();
5042
- if (isClientConfig(config)) {
5043
- this.#db = config.client;
6072
+ this.schema = parseSqlIdentifier(config.schemaName || "public", "schema name");
6073
+ if (isPoolConfig(config)) {
6074
+ this.#pool = config.pool;
6075
+ this.#ownsPool = false;
5044
6076
  } else {
5045
- let pgConfig;
5046
- if (isConnectionStringConfig(config)) {
5047
- pgConfig = {
5048
- id: config.id,
5049
- connectionString: config.connectionString,
5050
- max: config.max,
5051
- idleTimeoutMillis: config.idleTimeoutMillis,
5052
- ssl: config.ssl
5053
- };
5054
- } else if (isCloudSqlConfig(config)) {
5055
- pgConfig = {
5056
- ...config,
5057
- id: config.id,
5058
- max: config.max,
5059
- idleTimeoutMillis: config.idleTimeoutMillis
5060
- };
5061
- } else if (isHostConfig(config)) {
5062
- pgConfig = {
5063
- id: config.id,
5064
- host: config.host,
5065
- port: config.port,
5066
- database: config.database,
5067
- user: config.user,
5068
- password: config.password,
5069
- ssl: config.ssl,
5070
- max: config.max,
5071
- idleTimeoutMillis: config.idleTimeoutMillis
5072
- };
5073
- } else {
5074
- throw new Error(
5075
- "PostgresStore: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
5076
- );
5077
- }
5078
- this.#db = this.#pgp(pgConfig);
5079
- }
5080
- const skipDefaultIndexes = config.skipDefaultIndexes;
5081
- const indexes = config.indexes;
5082
- const domainConfig = { client: this.#db, schemaName: this.schema, skipDefaultIndexes, indexes };
5083
- const scores = new ScoresPG(domainConfig);
5084
- const workflows = new WorkflowsPG(domainConfig);
5085
- const memory = new MemoryPG(domainConfig);
5086
- const observability = new ObservabilityPG(domainConfig);
5087
- const agents = new AgentsPG(domainConfig);
6077
+ this.#pool = this.createPool(config);
6078
+ this.#ownsPool = true;
6079
+ }
6080
+ this.#db = new PoolAdapter(this.#pool);
6081
+ const domainConfig = {
6082
+ client: this.#db,
6083
+ schemaName: this.schema,
6084
+ skipDefaultIndexes: config.skipDefaultIndexes,
6085
+ indexes: config.indexes
6086
+ };
5088
6087
  this.stores = {
5089
- scores,
5090
- workflows,
5091
- memory,
5092
- observability,
5093
- agents
6088
+ scores: new ScoresPG(domainConfig),
6089
+ workflows: new WorkflowsPG(domainConfig),
6090
+ memory: new MemoryPG(domainConfig),
6091
+ observability: new ObservabilityPG(domainConfig),
6092
+ agents: new AgentsPG(domainConfig)
5094
6093
  };
5095
6094
  } catch (e) {
5096
6095
  throw new MastraError(
@@ -5103,6 +6102,32 @@ var PostgresStore = class extends MastraStorage {
5103
6102
  );
5104
6103
  }
5105
6104
  }
6105
+ createPool(config) {
6106
+ if (isConnectionStringConfig(config)) {
6107
+ return new Pool({
6108
+ connectionString: config.connectionString,
6109
+ ssl: config.ssl,
6110
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
6111
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
6112
+ });
6113
+ }
6114
+ if (isHostConfig(config)) {
6115
+ return new Pool({
6116
+ host: config.host,
6117
+ port: config.port,
6118
+ database: config.database,
6119
+ user: config.user,
6120
+ password: config.password,
6121
+ ssl: config.ssl,
6122
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
6123
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
6124
+ });
6125
+ }
6126
+ if (isCloudSqlConfig(config)) {
6127
+ return new Pool(config);
6128
+ }
6129
+ throw new Error("PostgresStore: invalid config");
6130
+ }
5106
6131
  async init() {
5107
6132
  if (this.isInitialized) {
5108
6133
  return;
@@ -5112,6 +6137,9 @@ var PostgresStore = class extends MastraStorage {
5112
6137
  await super.init();
5113
6138
  } catch (error) {
5114
6139
  this.isInitialized = false;
6140
+ if (error instanceof MastraError) {
6141
+ throw error;
6142
+ }
5115
6143
  throw new MastraError(
5116
6144
  {
5117
6145
  id: createStorageErrorId("PG", "INIT", "FAILED"),
@@ -5122,32 +6150,32 @@ var PostgresStore = class extends MastraStorage {
5122
6150
  );
5123
6151
  }
5124
6152
  }
6153
+ /**
6154
+ * Database client for executing queries.
6155
+ *
6156
+ * @example
6157
+ * ```typescript
6158
+ * const rows = await store.db.any('SELECT * FROM users WHERE active = $1', [true]);
6159
+ * const user = await store.db.one('SELECT * FROM users WHERE id = $1', [userId]);
6160
+ * ```
6161
+ */
5125
6162
  get db() {
5126
6163
  return this.#db;
5127
6164
  }
5128
- get pgp() {
5129
- return this.#pgp;
5130
- }
5131
- get supports() {
5132
- return {
5133
- selectByIncludeResourceScope: true,
5134
- resourceWorkingMemory: true,
5135
- hasColumn: true,
5136
- createTable: true,
5137
- deleteMessages: true,
5138
- observability: true,
5139
- indexManagement: true,
5140
- listScoresBySpan: true,
5141
- agents: true
5142
- };
6165
+ /**
6166
+ * The underlying pg.Pool for direct database access or ORM integration.
6167
+ */
6168
+ get pool() {
6169
+ return this.#pool;
5143
6170
  }
5144
6171
  /**
5145
- * Closes the pg-promise connection pool.
5146
- *
5147
- * This will close ALL connections in the pool, including pre-configured clients.
6172
+ * Closes the connection pool if it was created by this store.
6173
+ * If a pool was passed in via config, it will not be closed.
5148
6174
  */
5149
6175
  async close() {
5150
- this.pgp.end();
6176
+ if (this.#ownsPool) {
6177
+ await this.#pool.end();
6178
+ }
5151
6179
  }
5152
6180
  };
5153
6181
 
@@ -5250,6 +6278,6 @@ Example Complex Query:
5250
6278
  ]
5251
6279
  }`;
5252
6280
 
5253
- export { PGVECTOR_PROMPT, PgVector, PostgresStore };
6281
+ export { AgentsPG, MemoryPG, ObservabilityPG, PGVECTOR_PROMPT, PgVector, PoolAdapter, PostgresStore, ScoresPG, WorkflowsPG, exportSchemas };
5254
6282
  //# sourceMappingURL=index.js.map
5255
6283
  //# sourceMappingURL=index.js.map