@mastra/pg 1.0.0-beta.10 → 1.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8,7 +8,6 @@ var asyncMutex = require('async-mutex');
8
8
  var pg = require('pg');
9
9
  var xxhash = require('xxhash-wasm');
10
10
  var filter = require('@mastra/core/vector/filter');
11
- var pgPromise = require('pg-promise');
12
11
  var base = require('@mastra/core/base');
13
12
  var agent = require('@mastra/core/agent');
14
13
  var evals = require('@mastra/core/evals');
@@ -35,13 +34,15 @@ function _interopNamespace(e) {
35
34
 
36
35
  var pg__namespace = /*#__PURE__*/_interopNamespace(pg);
37
36
  var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
38
- var pgPromise__default = /*#__PURE__*/_interopDefault(pgPromise);
39
37
 
40
38
  // src/vector/index.ts
41
39
 
42
40
  // src/shared/config.ts
41
+ var isPoolConfig = (cfg) => {
42
+ return "pool" in cfg;
43
+ };
43
44
  var isConnectionStringConfig = (cfg) => {
44
- return "connectionString" in cfg;
45
+ return "connectionString" in cfg && typeof cfg.connectionString === "string";
45
46
  };
46
47
  var isHostConfig = (cfg) => {
47
48
  return "host" in cfg && "database" in cfg && "user" in cfg && "password" in cfg;
@@ -49,16 +50,13 @@ var isHostConfig = (cfg) => {
49
50
  var isCloudSqlConfig = (cfg) => {
50
51
  return "stream" in cfg || "password" in cfg && typeof cfg.password === "function";
51
52
  };
52
- var isClientConfig = (cfg) => {
53
- return "client" in cfg;
54
- };
55
53
  var validateConfig = (name, config) => {
56
54
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
57
55
  throw new Error(`${name}: id must be provided and cannot be empty.`);
58
56
  }
59
- if (isClientConfig(config)) {
60
- if (!config.client) {
61
- throw new Error(`${name}: client must be provided when using client config.`);
57
+ if (isPoolConfig(config)) {
58
+ if (!config.pool) {
59
+ throw new Error(`${name}: pool must be provided when using pool config.`);
62
60
  }
63
61
  return;
64
62
  }
@@ -79,7 +77,7 @@ var validateConfig = (name, config) => {
79
77
  }
80
78
  } else {
81
79
  throw new Error(
82
- `${name}: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with \`stream\`).`
80
+ `${name}: invalid config. Provide either {pool}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with \`stream\`).`
83
81
  );
84
82
  }
85
83
  };
@@ -544,8 +542,8 @@ var PgVector = class extends vector.MastraVector {
544
542
  } else if (isCloudSqlConfig(config)) {
545
543
  poolConfig = {
546
544
  ...config,
547
- max: config.max ?? 20,
548
- idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
545
+ max: config.pgPoolOptions?.max ?? 20,
546
+ idleTimeoutMillis: config.pgPoolOptions?.idleTimeoutMillis ?? 3e4,
549
547
  connectionTimeoutMillis: 2e3,
550
548
  ...config.pgPoolOptions
551
549
  };
@@ -1687,6 +1685,132 @@ var PgVector = class extends vector.MastraVector {
1687
1685
  }
1688
1686
  }
1689
1687
  };
1688
+
1689
+ // src/storage/client.ts
1690
+ function truncateQuery(query, maxLength = 100) {
1691
+ const normalized = query.replace(/\s+/g, " ").trim();
1692
+ if (normalized.length <= maxLength) {
1693
+ return normalized;
1694
+ }
1695
+ return normalized.slice(0, maxLength) + "...";
1696
+ }
1697
+ var PoolAdapter = class {
1698
+ constructor($pool) {
1699
+ this.$pool = $pool;
1700
+ }
1701
+ connect() {
1702
+ return this.$pool.connect();
1703
+ }
1704
+ async none(query, values) {
1705
+ await this.$pool.query(query, values);
1706
+ return null;
1707
+ }
1708
+ async one(query, values) {
1709
+ const result = await this.$pool.query(query, values);
1710
+ if (result.rows.length === 0) {
1711
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1712
+ }
1713
+ if (result.rows.length > 1) {
1714
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1715
+ }
1716
+ return result.rows[0];
1717
+ }
1718
+ async oneOrNone(query, values) {
1719
+ const result = await this.$pool.query(query, values);
1720
+ if (result.rows.length === 0) {
1721
+ return null;
1722
+ }
1723
+ if (result.rows.length > 1) {
1724
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1725
+ }
1726
+ return result.rows[0];
1727
+ }
1728
+ async any(query, values) {
1729
+ const result = await this.$pool.query(query, values);
1730
+ return result.rows;
1731
+ }
1732
+ async manyOrNone(query, values) {
1733
+ return this.any(query, values);
1734
+ }
1735
+ async many(query, values) {
1736
+ const result = await this.$pool.query(query, values);
1737
+ if (result.rows.length === 0) {
1738
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1739
+ }
1740
+ return result.rows;
1741
+ }
1742
+ async query(query, values) {
1743
+ return this.$pool.query(query, values);
1744
+ }
1745
+ async tx(callback) {
1746
+ const client = await this.$pool.connect();
1747
+ try {
1748
+ await client.query("BEGIN");
1749
+ const txClient = new TransactionClient(client);
1750
+ const result = await callback(txClient);
1751
+ await client.query("COMMIT");
1752
+ return result;
1753
+ } catch (error) {
1754
+ try {
1755
+ await client.query("ROLLBACK");
1756
+ } catch (rollbackError) {
1757
+ console.error("Transaction rollback failed:", rollbackError);
1758
+ }
1759
+ throw error;
1760
+ } finally {
1761
+ client.release();
1762
+ }
1763
+ }
1764
+ };
1765
+ var TransactionClient = class {
1766
+ constructor(client) {
1767
+ this.client = client;
1768
+ }
1769
+ async none(query, values) {
1770
+ await this.client.query(query, values);
1771
+ return null;
1772
+ }
1773
+ async one(query, values) {
1774
+ const result = await this.client.query(query, values);
1775
+ if (result.rows.length === 0) {
1776
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1777
+ }
1778
+ if (result.rows.length > 1) {
1779
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1780
+ }
1781
+ return result.rows[0];
1782
+ }
1783
+ async oneOrNone(query, values) {
1784
+ const result = await this.client.query(query, values);
1785
+ if (result.rows.length === 0) {
1786
+ return null;
1787
+ }
1788
+ if (result.rows.length > 1) {
1789
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1790
+ }
1791
+ return result.rows[0];
1792
+ }
1793
+ async any(query, values) {
1794
+ const result = await this.client.query(query, values);
1795
+ return result.rows;
1796
+ }
1797
+ async manyOrNone(query, values) {
1798
+ return this.any(query, values);
1799
+ }
1800
+ async many(query, values) {
1801
+ const result = await this.client.query(query, values);
1802
+ if (result.rows.length === 0) {
1803
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1804
+ }
1805
+ return result.rows;
1806
+ }
1807
+ async query(query, values) {
1808
+ return this.client.query(query, values);
1809
+ }
1810
+ async batch(promises) {
1811
+ return Promise.all(promises);
1812
+ }
1813
+ };
1690
1814
  function resolvePgConfig(config) {
1691
1815
  if ("client" in config) {
1692
1816
  return {
@@ -1696,10 +1820,32 @@ function resolvePgConfig(config) {
1696
1820
  indexes: config.indexes
1697
1821
  };
1698
1822
  }
1699
- const pgp = pgPromise__default.default();
1700
- const client = pgp(config);
1823
+ if ("pool" in config) {
1824
+ return {
1825
+ client: new PoolAdapter(config.pool),
1826
+ schemaName: config.schemaName,
1827
+ skipDefaultIndexes: config.skipDefaultIndexes,
1828
+ indexes: config.indexes
1829
+ };
1830
+ }
1831
+ let pool;
1832
+ if ("connectionString" in config) {
1833
+ pool = new pg.Pool({
1834
+ connectionString: config.connectionString,
1835
+ ssl: config.ssl
1836
+ });
1837
+ } else {
1838
+ pool = new pg.Pool({
1839
+ host: config.host,
1840
+ port: config.port,
1841
+ database: config.database,
1842
+ user: config.user,
1843
+ password: config.password,
1844
+ ssl: config.ssl
1845
+ });
1846
+ }
1701
1847
  return {
1702
- client,
1848
+ client: new PoolAdapter(pool),
1703
1849
  schemaName: config.schemaName,
1704
1850
  skipDefaultIndexes: config.skipDefaultIndexes,
1705
1851
  indexes: config.indexes
@@ -1714,6 +1860,87 @@ function getTableName({ indexName, schemaName }) {
1714
1860
  const quotedSchemaName = schemaName;
1715
1861
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1716
1862
  }
1863
+ function mapToSqlType(type) {
1864
+ switch (type) {
1865
+ case "uuid":
1866
+ return "UUID";
1867
+ case "boolean":
1868
+ return "BOOLEAN";
1869
+ default:
1870
+ return storage.getSqlType(type);
1871
+ }
1872
+ }
1873
+ function generateTableSQL({
1874
+ tableName,
1875
+ schema,
1876
+ schemaName
1877
+ }) {
1878
+ const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
1879
+ const parsedName = utils.parseSqlIdentifier(name, "column name");
1880
+ return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
1881
+ });
1882
+ const columns = Object.entries(schema).map(([name, def]) => {
1883
+ const parsedName = utils.parseSqlIdentifier(name, "column name");
1884
+ const constraints = [];
1885
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
1886
+ if (!def.nullable) constraints.push("NOT NULL");
1887
+ return `"${parsedName}" ${mapToSqlType(def.type)} ${constraints.join(" ")}`;
1888
+ });
1889
+ const finalColumns = [...columns, ...timeZColumns].join(",\n");
1890
+ const parsedSchemaName = schemaName ? utils.parseSqlIdentifier(schemaName, "schema name") : "";
1891
+ const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
1892
+ const quotedSchemaName = getSchemaName(schemaName);
1893
+ const sql = `
1894
+ CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })} (
1895
+ ${finalColumns}
1896
+ );
1897
+ ${tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? `
1898
+ DO $$ BEGIN
1899
+ IF NOT EXISTS (
1900
+ SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1901
+ ) AND NOT EXISTS (
1902
+ SELECT 1 FROM pg_indexes WHERE indexname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1903
+ ) THEN
1904
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1905
+ ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
1906
+ UNIQUE (workflow_name, run_id);
1907
+ END IF;
1908
+ END $$;
1909
+ ` : ""}
1910
+ ${tableName === storage.TABLE_SPANS ? `
1911
+ DO $$ BEGIN
1912
+ IF NOT EXISTS (
1913
+ SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_ai_spans_traceid_spanid_pk'
1914
+ ) THEN
1915
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1916
+ ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
1917
+ PRIMARY KEY ("traceId", "spanId");
1918
+ END IF;
1919
+ END $$;
1920
+ ` : ""}
1921
+ `;
1922
+ return sql;
1923
+ }
1924
+ function exportSchemas(schemaName) {
1925
+ const statements = [];
1926
+ if (schemaName) {
1927
+ const quotedSchemaName = getSchemaName(schemaName);
1928
+ statements.push(`-- Create schema if it doesn't exist`);
1929
+ statements.push(`CREATE SCHEMA IF NOT EXISTS ${quotedSchemaName};`);
1930
+ statements.push("");
1931
+ }
1932
+ for (const [tableName, schema] of Object.entries(storage.TABLE_SCHEMAS)) {
1933
+ statements.push(`-- Table: ${tableName}`);
1934
+ const sql = generateTableSQL({
1935
+ tableName,
1936
+ schema,
1937
+ schemaName
1938
+ });
1939
+ statements.push(sql.trim());
1940
+ statements.push("");
1941
+ }
1942
+ return statements.join("\n");
1943
+ }
1717
1944
  var schemaSetupRegistry = /* @__PURE__ */ new Map();
1718
1945
  var PgDB = class extends base.MastraBase {
1719
1946
  client;
@@ -1831,16 +2058,6 @@ var PgDB = class extends base.MastraBase {
1831
2058
  }
1832
2059
  await registryEntry.promise;
1833
2060
  }
1834
- getSqlType(type) {
1835
- switch (type) {
1836
- case "uuid":
1837
- return "UUID";
1838
- case "boolean":
1839
- return "BOOLEAN";
1840
- default:
1841
- return storage.getSqlType(type);
1842
- }
1843
- }
1844
2061
  getDefaultValue(type) {
1845
2062
  switch (type) {
1846
2063
  case "timestamp":
@@ -1910,52 +2127,10 @@ var PgDB = class extends base.MastraBase {
1910
2127
  }) {
1911
2128
  try {
1912
2129
  const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
1913
- const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
1914
- const parsedName = utils.parseSqlIdentifier(name, "column name");
1915
- return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
1916
- });
1917
- const columns = Object.entries(schema).map(([name, def]) => {
1918
- const parsedName = utils.parseSqlIdentifier(name, "column name");
1919
- const constraints = [];
1920
- if (def.primaryKey) constraints.push("PRIMARY KEY");
1921
- if (!def.nullable) constraints.push("NOT NULL");
1922
- return `"${parsedName}" ${this.getSqlType(def.type)} ${constraints.join(" ")}`;
1923
- });
1924
2130
  if (this.schemaName) {
1925
2131
  await this.setupSchema();
1926
2132
  }
1927
- const finalColumns = [...columns, ...timeZColumns].join(",\n");
1928
- const constraintPrefix = this.schemaName ? `${this.schemaName}_` : "";
1929
- const schemaName = getSchemaName(this.schemaName);
1930
- const sql = `
1931
- CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName })} (
1932
- ${finalColumns}
1933
- );
1934
- ${tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? `
1935
- DO $$ BEGIN
1936
- IF NOT EXISTS (
1937
- SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1938
- ) AND NOT EXISTS (
1939
- SELECT 1 FROM pg_indexes WHERE indexname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1940
- ) THEN
1941
- ALTER TABLE ${getTableName({ indexName: tableName, schemaName })}
1942
- ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
1943
- UNIQUE (workflow_name, run_id);
1944
- END IF;
1945
- END $$;
1946
- ` : ""}
1947
- ${tableName === storage.TABLE_SPANS ? `
1948
- DO $$ BEGIN
1949
- IF NOT EXISTS (
1950
- SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_ai_spans_traceid_spanid_pk'
1951
- ) THEN
1952
- ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })}
1953
- ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
1954
- PRIMARY KEY ("traceId", "spanId");
1955
- END IF;
1956
- END $$;
1957
- ` : ""}
1958
- `;
2133
+ const sql = generateTableSQL({ tableName, schema, schemaName: this.schemaName });
1959
2134
  await this.client.none(sql);
1960
2135
  await this.alterTable({
1961
2136
  tableName,
@@ -2029,7 +2204,7 @@ var PgDB = class extends base.MastraBase {
2029
2204
  const columnExists = await this.hasColumn(storage.TABLE_SPANS, columnName);
2030
2205
  if (!columnExists) {
2031
2206
  const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
2032
- const sqlType = this.getSqlType(columnDef.type);
2207
+ const sqlType = mapToSqlType(columnDef.type);
2033
2208
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2034
2209
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2035
2210
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
@@ -2076,7 +2251,7 @@ var PgDB = class extends base.MastraBase {
2076
2251
  if (schema[columnName]) {
2077
2252
  const columnDef = schema[columnName];
2078
2253
  const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
2079
- const sqlType = this.getSqlType(columnDef.type);
2254
+ const sqlType = mapToSqlType(columnDef.type);
2080
2255
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2081
2256
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2082
2257
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
@@ -2396,9 +2571,9 @@ var PgDB = class extends base.MastraBase {
2396
2571
  size: result.size || "0",
2397
2572
  definition: result.definition || "",
2398
2573
  method: result.method || "btree",
2399
- scans: parseInt(result.scans) || 0,
2400
- tuples_read: parseInt(result.tuples_read) || 0,
2401
- tuples_fetched: parseInt(result.tuples_fetched) || 0
2574
+ scans: parseInt(String(result.scans)) || 0,
2575
+ tuples_read: parseInt(String(result.tuples_read)) || 0,
2576
+ tuples_fetched: parseInt(String(result.tuples_fetched)) || 0
2402
2577
  };
2403
2578
  } catch (error$1) {
2404
2579
  throw new error.MastraError(
@@ -2915,6 +3090,9 @@ function getTableName3({ indexName, schemaName }) {
2915
3090
  const quotedIndexName = `"${indexName}"`;
2916
3091
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
2917
3092
  }
3093
+ function inPlaceholders(count, startIndex = 1) {
3094
+ return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
3095
+ }
2918
3096
  var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
2919
3097
  #db;
2920
3098
  #schema;
@@ -3075,13 +3253,19 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3075
3253
  };
3076
3254
  }
3077
3255
  const limitValue = perPageInput === false ? total : perPage;
3078
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`;
3079
- const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
3256
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`;
3257
+ const rows = await this.#db.client.manyOrNone(
3258
+ dataQuery,
3259
+ [...queryParams, limitValue, offset]
3260
+ );
3080
3261
  const threads = (rows || []).map((thread) => ({
3081
- ...thread,
3262
+ id: thread.id,
3263
+ resourceId: thread.resourceId,
3264
+ title: thread.title,
3082
3265
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
3083
- createdAt: thread.createdAt,
3084
- updatedAt: thread.updatedAt
3266
+ // Use timezone-aware columns (*Z) for correct UTC timestamps, with fallback for legacy data
3267
+ createdAt: thread.createdAtZ || thread.createdAt,
3268
+ updatedAt: thread.updatedAtZ || thread.updatedAt
3085
3269
  }));
3086
3270
  return {
3087
3271
  threads,
@@ -3186,17 +3370,18 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3186
3370
  ...metadata
3187
3371
  };
3188
3372
  try {
3373
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3189
3374
  const thread = await this.#db.client.one(
3190
3375
  `UPDATE ${threadTableName}
3191
3376
  SET
3192
3377
  title = $1,
3193
3378
  metadata = $2,
3194
3379
  "updatedAt" = $3,
3195
- "updatedAtZ" = $3
3196
- WHERE id = $4
3380
+ "updatedAtZ" = $4
3381
+ WHERE id = $5
3197
3382
  RETURNING *
3198
3383
  `,
3199
- [title, mergedMetadata, (/* @__PURE__ */ new Date()).toISOString(), id]
3384
+ [title, mergedMetadata, now, now, id]
3200
3385
  );
3201
3386
  return {
3202
3387
  id: thread.id,
@@ -3339,7 +3524,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3339
3524
  const tableName = getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3340
3525
  const query = `
3341
3526
  ${selectStatement} FROM ${tableName}
3342
- WHERE id IN (${messageIds.map((_, i) => `$${i + 1}`).join(", ")})
3527
+ WHERE id IN (${inPlaceholders(messageIds.length)})
3343
3528
  ORDER BY "createdAt" DESC
3344
3529
  `;
3345
3530
  const resultRows = await this.#db.client.manyOrNone(query, messageIds);
@@ -3400,8 +3585,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3400
3585
  const orderByStatement = `ORDER BY "${field}" ${direction}`;
3401
3586
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
3402
3587
  const tableName = getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3403
- const threadPlaceholders = threadIds.map((_, i) => `$${i + 1}`).join(", ");
3404
- const conditions = [`thread_id IN (${threadPlaceholders})`];
3588
+ const conditions = [`thread_id IN (${inPlaceholders(threadIds.length)})`];
3405
3589
  const queryParams = [...threadIds];
3406
3590
  let paramIndex = threadIds.length + 1;
3407
3591
  if (resourceId) {
@@ -3409,11 +3593,13 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3409
3593
  queryParams.push(resourceId);
3410
3594
  }
3411
3595
  if (filter?.dateRange?.start) {
3412
- conditions.push(`"createdAt" >= $${paramIndex++}`);
3596
+ const startOp = filter.dateRange.startExclusive ? ">" : ">=";
3597
+ conditions.push(`"createdAt" ${startOp} $${paramIndex++}`);
3413
3598
  queryParams.push(filter.dateRange.start);
3414
3599
  }
3415
3600
  if (filter?.dateRange?.end) {
3416
- conditions.push(`"createdAt" <= $${paramIndex++}`);
3601
+ const endOp = filter.dateRange.endExclusive ? "<" : "<=";
3602
+ conditions.push(`"createdAt" ${endOp} $${paramIndex++}`);
3417
3603
  queryParams.push(filter.dateRange.end);
3418
3604
  }
3419
3605
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
@@ -3558,14 +3744,15 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3558
3744
  );
3559
3745
  });
3560
3746
  const threadTableName = getTableName3({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
3747
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
3561
3748
  const threadUpdate = t.none(
3562
3749
  `UPDATE ${threadTableName}
3563
3750
  SET
3564
3751
  "updatedAt" = $1,
3565
- "updatedAtZ" = $1
3566
- WHERE id = $2
3752
+ "updatedAtZ" = $2
3753
+ WHERE id = $3
3567
3754
  `,
3568
- [(/* @__PURE__ */ new Date()).toISOString(), threadId]
3755
+ [nowStr, nowStr, threadId]
3569
3756
  );
3570
3757
  await Promise.all([...messageInserts, threadUpdate]);
3571
3758
  });
@@ -3602,8 +3789,8 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3602
3789
  return [];
3603
3790
  }
3604
3791
  const messageIds = messages.map((m) => m.id);
3605
- const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN ($1:list)`;
3606
- const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
3792
+ const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) })} WHERE id IN (${inPlaceholders(messageIds.length)})`;
3793
+ const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, messageIds);
3607
3794
  if (existingMessagesDb.length === 0) {
3608
3795
  return [];
3609
3796
  }
@@ -3664,10 +3851,11 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3664
3851
  }
3665
3852
  }
3666
3853
  if (threadIdsToUpdate.size > 0) {
3854
+ const threadIds = Array.from(threadIdsToUpdate);
3667
3855
  queries.push(
3668
3856
  t.none(
3669
- `UPDATE ${getTableName3({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN ($1:list)`,
3670
- [Array.from(threadIdsToUpdate)]
3857
+ `UPDATE ${getTableName3({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`,
3858
+ threadIds
3671
3859
  )
3672
3860
  );
3673
3861
  }
@@ -3675,7 +3863,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3675
3863
  await t.batch(queries);
3676
3864
  }
3677
3865
  });
3678
- const updatedMessages = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
3866
+ const updatedMessages = await this.#db.client.manyOrNone(selectQuery, messageIds);
3679
3867
  return (updatedMessages || []).map((row) => {
3680
3868
  const message = this.normalizeMessageRow(row);
3681
3869
  if (typeof message.content === "string") {
@@ -3787,15 +3975,159 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
3787
3975
  values.push(JSON.stringify(updatedResource.metadata));
3788
3976
  paramIndex++;
3789
3977
  }
3790
- updates.push(`"updatedAt" = $${paramIndex}`);
3791
- values.push(updatedResource.updatedAt.toISOString());
3978
+ const updatedAtStr = updatedResource.updatedAt.toISOString();
3979
+ updates.push(`"updatedAt" = $${paramIndex++}`);
3980
+ values.push(updatedAtStr);
3792
3981
  updates.push(`"updatedAtZ" = $${paramIndex++}`);
3793
- values.push(updatedResource.updatedAt.toISOString());
3794
- paramIndex++;
3982
+ values.push(updatedAtStr);
3795
3983
  values.push(resourceId);
3796
3984
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
3797
3985
  return updatedResource;
3798
3986
  }
3987
+ async cloneThread(args) {
3988
+ const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
3989
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
3990
+ if (!sourceThread) {
3991
+ throw new error.MastraError({
3992
+ id: storage.createStorageErrorId("PG", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
3993
+ domain: error.ErrorDomain.STORAGE,
3994
+ category: error.ErrorCategory.USER,
3995
+ text: `Source thread with id ${sourceThreadId} not found`,
3996
+ details: { sourceThreadId }
3997
+ });
3998
+ }
3999
+ const newThreadId = providedThreadId || crypto.randomUUID();
4000
+ const existingThread = await this.getThreadById({ threadId: newThreadId });
4001
+ if (existingThread) {
4002
+ throw new error.MastraError({
4003
+ id: storage.createStorageErrorId("PG", "CLONE_THREAD", "THREAD_EXISTS"),
4004
+ domain: error.ErrorDomain.STORAGE,
4005
+ category: error.ErrorCategory.USER,
4006
+ text: `Thread with id ${newThreadId} already exists`,
4007
+ details: { newThreadId }
4008
+ });
4009
+ }
4010
+ const threadTableName = getTableName3({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
4011
+ const messageTableName = getTableName3({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
4012
+ try {
4013
+ return await this.#db.client.tx(async (t) => {
4014
+ let messageQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"
4015
+ FROM ${messageTableName} WHERE thread_id = $1`;
4016
+ const messageParams = [sourceThreadId];
4017
+ let paramIndex = 2;
4018
+ if (options?.messageFilter?.startDate) {
4019
+ messageQuery += ` AND "createdAt" >= $${paramIndex++}`;
4020
+ messageParams.push(options.messageFilter.startDate);
4021
+ }
4022
+ if (options?.messageFilter?.endDate) {
4023
+ messageQuery += ` AND "createdAt" <= $${paramIndex++}`;
4024
+ messageParams.push(options.messageFilter.endDate);
4025
+ }
4026
+ if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
4027
+ messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => `$${paramIndex++}`).join(", ")})`;
4028
+ messageParams.push(...options.messageFilter.messageIds);
4029
+ }
4030
+ messageQuery += ` ORDER BY "createdAt" ASC`;
4031
+ if (options?.messageLimit && options.messageLimit > 0) {
4032
+ const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT $${paramIndex}) AS limited ORDER BY "createdAt" ASC`;
4033
+ messageParams.push(options.messageLimit);
4034
+ messageQuery = limitQuery;
4035
+ }
4036
+ const sourceMessages = await t.manyOrNone(messageQuery, messageParams);
4037
+ const now = /* @__PURE__ */ new Date();
4038
+ const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
4039
+ const cloneMetadata = {
4040
+ sourceThreadId,
4041
+ clonedAt: now,
4042
+ ...lastMessageId && { lastMessageId }
4043
+ };
4044
+ const newThread = {
4045
+ id: newThreadId,
4046
+ resourceId: resourceId || sourceThread.resourceId,
4047
+ title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
4048
+ metadata: {
4049
+ ...metadata,
4050
+ clone: cloneMetadata
4051
+ },
4052
+ createdAt: now,
4053
+ updatedAt: now
4054
+ };
4055
+ await t.none(
4056
+ `INSERT INTO ${threadTableName} (
4057
+ id,
4058
+ "resourceId",
4059
+ title,
4060
+ metadata,
4061
+ "createdAt",
4062
+ "createdAtZ",
4063
+ "updatedAt",
4064
+ "updatedAtZ"
4065
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4066
+ [
4067
+ newThread.id,
4068
+ newThread.resourceId,
4069
+ newThread.title,
4070
+ newThread.metadata ? JSON.stringify(newThread.metadata) : null,
4071
+ now,
4072
+ now,
4073
+ now,
4074
+ now
4075
+ ]
4076
+ );
4077
+ const clonedMessages = [];
4078
+ const targetResourceId = resourceId || sourceThread.resourceId;
4079
+ for (const sourceMsg of sourceMessages) {
4080
+ const newMessageId = crypto.randomUUID();
4081
+ const normalizedMsg = this.normalizeMessageRow(sourceMsg);
4082
+ let parsedContent = normalizedMsg.content;
4083
+ try {
4084
+ parsedContent = JSON.parse(normalizedMsg.content);
4085
+ } catch {
4086
+ }
4087
+ await t.none(
4088
+ `INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
4089
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4090
+ [
4091
+ newMessageId,
4092
+ newThreadId,
4093
+ typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
4094
+ normalizedMsg.createdAt,
4095
+ normalizedMsg.createdAt,
4096
+ normalizedMsg.role,
4097
+ normalizedMsg.type || "v2",
4098
+ targetResourceId
4099
+ ]
4100
+ );
4101
+ clonedMessages.push({
4102
+ id: newMessageId,
4103
+ threadId: newThreadId,
4104
+ content: parsedContent,
4105
+ role: normalizedMsg.role,
4106
+ type: normalizedMsg.type,
4107
+ createdAt: new Date(normalizedMsg.createdAt),
4108
+ resourceId: targetResourceId
4109
+ });
4110
+ }
4111
+ return {
4112
+ thread: newThread,
4113
+ clonedMessages
4114
+ };
4115
+ });
4116
+ } catch (error$1) {
4117
+ if (error$1 instanceof error.MastraError) {
4118
+ throw error$1;
4119
+ }
4120
+ throw new error.MastraError(
4121
+ {
4122
+ id: storage.createStorageErrorId("PG", "CLONE_THREAD", "FAILED"),
4123
+ domain: error.ErrorDomain.STORAGE,
4124
+ category: error.ErrorCategory.THIRD_PARTY,
4125
+ details: { sourceThreadId, newThreadId }
4126
+ },
4127
+ error$1
4128
+ );
4129
+ }
4130
+ }
3799
4131
  };
3800
4132
  var ObservabilityPG = class _ObservabilityPG extends storage.ObservabilityStorage {
3801
4133
  #db;
@@ -5070,9 +5402,12 @@ var WorkflowsPG = class _WorkflowsPG extends storage.WorkflowsStorage {
5070
5402
  };
5071
5403
 
5072
5404
  // src/storage/index.ts
5405
+ var DEFAULT_MAX_CONNECTIONS = 20;
5406
+ var DEFAULT_IDLE_TIMEOUT_MS = 3e4;
5073
5407
  var PostgresStore = class extends storage.MastraStorage {
5408
+ #pool;
5074
5409
  #db;
5075
- #pgp;
5410
+ #ownsPool;
5076
5411
  schema;
5077
5412
  isInitialized = false;
5078
5413
  stores;
@@ -5081,59 +5416,26 @@ var PostgresStore = class extends storage.MastraStorage {
5081
5416
  validateConfig("PostgresStore", config);
5082
5417
  super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit });
5083
5418
  this.schema = config.schemaName || "public";
5084
- this.#pgp = pgPromise__default.default();
5085
- if (isClientConfig(config)) {
5086
- this.#db = config.client;
5419
+ if (isPoolConfig(config)) {
5420
+ this.#pool = config.pool;
5421
+ this.#ownsPool = false;
5087
5422
  } else {
5088
- let pgConfig;
5089
- if (isConnectionStringConfig(config)) {
5090
- pgConfig = {
5091
- id: config.id,
5092
- connectionString: config.connectionString,
5093
- max: config.max,
5094
- idleTimeoutMillis: config.idleTimeoutMillis,
5095
- ssl: config.ssl
5096
- };
5097
- } else if (isCloudSqlConfig(config)) {
5098
- pgConfig = {
5099
- ...config,
5100
- id: config.id,
5101
- max: config.max,
5102
- idleTimeoutMillis: config.idleTimeoutMillis
5103
- };
5104
- } else if (isHostConfig(config)) {
5105
- pgConfig = {
5106
- id: config.id,
5107
- host: config.host,
5108
- port: config.port,
5109
- database: config.database,
5110
- user: config.user,
5111
- password: config.password,
5112
- ssl: config.ssl,
5113
- max: config.max,
5114
- idleTimeoutMillis: config.idleTimeoutMillis
5115
- };
5116
- } else {
5117
- throw new Error(
5118
- "PostgresStore: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
5119
- );
5120
- }
5121
- this.#db = this.#pgp(pgConfig);
5122
- }
5123
- const skipDefaultIndexes = config.skipDefaultIndexes;
5124
- const indexes = config.indexes;
5125
- const domainConfig = { client: this.#db, schemaName: this.schema, skipDefaultIndexes, indexes };
5126
- const scores = new ScoresPG(domainConfig);
5127
- const workflows = new WorkflowsPG(domainConfig);
5128
- const memory = new MemoryPG(domainConfig);
5129
- const observability = new ObservabilityPG(domainConfig);
5130
- const agents = new AgentsPG(domainConfig);
5423
+ this.#pool = this.createPool(config);
5424
+ this.#ownsPool = true;
5425
+ }
5426
+ this.#db = new PoolAdapter(this.#pool);
5427
+ const domainConfig = {
5428
+ client: this.#db,
5429
+ schemaName: this.schema,
5430
+ skipDefaultIndexes: config.skipDefaultIndexes,
5431
+ indexes: config.indexes
5432
+ };
5131
5433
  this.stores = {
5132
- scores,
5133
- workflows,
5134
- memory,
5135
- observability,
5136
- agents
5434
+ scores: new ScoresPG(domainConfig),
5435
+ workflows: new WorkflowsPG(domainConfig),
5436
+ memory: new MemoryPG(domainConfig),
5437
+ observability: new ObservabilityPG(domainConfig),
5438
+ agents: new AgentsPG(domainConfig)
5137
5439
  };
5138
5440
  } catch (e) {
5139
5441
  throw new error.MastraError(
@@ -5146,6 +5448,32 @@ var PostgresStore = class extends storage.MastraStorage {
5146
5448
  );
5147
5449
  }
5148
5450
  }
5451
+ createPool(config) {
5452
+ if (isConnectionStringConfig(config)) {
5453
+ return new pg.Pool({
5454
+ connectionString: config.connectionString,
5455
+ ssl: config.ssl,
5456
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
5457
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
5458
+ });
5459
+ }
5460
+ if (isHostConfig(config)) {
5461
+ return new pg.Pool({
5462
+ host: config.host,
5463
+ port: config.port,
5464
+ database: config.database,
5465
+ user: config.user,
5466
+ password: config.password,
5467
+ ssl: config.ssl,
5468
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
5469
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
5470
+ });
5471
+ }
5472
+ if (isCloudSqlConfig(config)) {
5473
+ return new pg.Pool(config);
5474
+ }
5475
+ throw new Error("PostgresStore: invalid config");
5476
+ }
5149
5477
  async init() {
5150
5478
  if (this.isInitialized) {
5151
5479
  return;
@@ -5165,19 +5493,32 @@ var PostgresStore = class extends storage.MastraStorage {
5165
5493
  );
5166
5494
  }
5167
5495
  }
5496
+ /**
5497
+ * Database client for executing queries.
5498
+ *
5499
+ * @example
5500
+ * ```typescript
5501
+ * const rows = await store.db.any('SELECT * FROM users WHERE active = $1', [true]);
5502
+ * const user = await store.db.one('SELECT * FROM users WHERE id = $1', [userId]);
5503
+ * ```
5504
+ */
5168
5505
  get db() {
5169
5506
  return this.#db;
5170
5507
  }
5171
- get pgp() {
5172
- return this.#pgp;
5508
+ /**
5509
+ * The underlying pg.Pool for direct database access or ORM integration.
5510
+ */
5511
+ get pool() {
5512
+ return this.#pool;
5173
5513
  }
5174
5514
  /**
5175
- * Closes the pg-promise connection pool.
5176
- *
5177
- * This will close ALL connections in the pool, including pre-configured clients.
5515
+ * Closes the connection pool if it was created by this store.
5516
+ * If a pool was passed in via config, it will not be closed.
5178
5517
  */
5179
5518
  async close() {
5180
- this.pgp.end();
5519
+ if (this.#ownsPool) {
5520
+ await this.#pool.end();
5521
+ }
5181
5522
  }
5182
5523
  };
5183
5524
 
@@ -5285,8 +5626,10 @@ exports.MemoryPG = MemoryPG;
5285
5626
  exports.ObservabilityPG = ObservabilityPG;
5286
5627
  exports.PGVECTOR_PROMPT = PGVECTOR_PROMPT;
5287
5628
  exports.PgVector = PgVector;
5629
+ exports.PoolAdapter = PoolAdapter;
5288
5630
  exports.PostgresStore = PostgresStore;
5289
5631
  exports.ScoresPG = ScoresPG;
5290
5632
  exports.WorkflowsPG = WorkflowsPG;
5633
+ exports.exportSchemas = exportSchemas;
5291
5634
  //# sourceMappingURL=index.cjs.map
5292
5635
  //# sourceMappingURL=index.cjs.map