@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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { createVectorErrorId, AgentsStorage, TABLE_AGENTS, TABLE_SCHEMAS, createStorageErrorId, normalizePerPage, calculatePagination, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, TraceStatus, getSqlType, getDefaultValue, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
2
+ import { createVectorErrorId, TABLE_SCHEMAS, AgentsStorage, TABLE_AGENTS, createStorageErrorId, normalizePerPage, calculatePagination, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, TraceStatus, getDefaultValue, transformScoreRow as transformScoreRow$1, getSqlType } from '@mastra/core/storage';
3
3
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
4
  import { MastraVector } 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
  };
@@ -518,8 +518,8 @@ var PgVector = class extends MastraVector {
518
518
  } else if (isCloudSqlConfig(config)) {
519
519
  poolConfig = {
520
520
  ...config,
521
- max: config.max ?? 20,
522
- idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
521
+ max: config.pgPoolOptions?.max ?? 20,
522
+ idleTimeoutMillis: config.pgPoolOptions?.idleTimeoutMillis ?? 3e4,
523
523
  connectionTimeoutMillis: 2e3,
524
524
  ...config.pgPoolOptions
525
525
  };
@@ -1661,6 +1661,132 @@ var PgVector = class extends MastraVector {
1661
1661
  }
1662
1662
  }
1663
1663
  };
1664
+
1665
+ // src/storage/client.ts
1666
+ function truncateQuery(query, maxLength = 100) {
1667
+ const normalized = query.replace(/\s+/g, " ").trim();
1668
+ if (normalized.length <= maxLength) {
1669
+ return normalized;
1670
+ }
1671
+ return normalized.slice(0, maxLength) + "...";
1672
+ }
1673
+ var PoolAdapter = class {
1674
+ constructor($pool) {
1675
+ this.$pool = $pool;
1676
+ }
1677
+ connect() {
1678
+ return this.$pool.connect();
1679
+ }
1680
+ async none(query, values) {
1681
+ await this.$pool.query(query, values);
1682
+ return null;
1683
+ }
1684
+ async one(query, values) {
1685
+ const result = await this.$pool.query(query, values);
1686
+ if (result.rows.length === 0) {
1687
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1688
+ }
1689
+ if (result.rows.length > 1) {
1690
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1691
+ }
1692
+ return result.rows[0];
1693
+ }
1694
+ async oneOrNone(query, values) {
1695
+ const result = await this.$pool.query(query, values);
1696
+ if (result.rows.length === 0) {
1697
+ return null;
1698
+ }
1699
+ if (result.rows.length > 1) {
1700
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1701
+ }
1702
+ return result.rows[0];
1703
+ }
1704
+ async any(query, values) {
1705
+ const result = await this.$pool.query(query, values);
1706
+ return result.rows;
1707
+ }
1708
+ async manyOrNone(query, values) {
1709
+ return this.any(query, values);
1710
+ }
1711
+ async many(query, values) {
1712
+ const result = await this.$pool.query(query, values);
1713
+ if (result.rows.length === 0) {
1714
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1715
+ }
1716
+ return result.rows;
1717
+ }
1718
+ async query(query, values) {
1719
+ return this.$pool.query(query, values);
1720
+ }
1721
+ async tx(callback) {
1722
+ const client = await this.$pool.connect();
1723
+ try {
1724
+ await client.query("BEGIN");
1725
+ const txClient = new TransactionClient(client);
1726
+ const result = await callback(txClient);
1727
+ await client.query("COMMIT");
1728
+ return result;
1729
+ } catch (error) {
1730
+ try {
1731
+ await client.query("ROLLBACK");
1732
+ } catch (rollbackError) {
1733
+ console.error("Transaction rollback failed:", rollbackError);
1734
+ }
1735
+ throw error;
1736
+ } finally {
1737
+ client.release();
1738
+ }
1739
+ }
1740
+ };
1741
+ var TransactionClient = class {
1742
+ constructor(client) {
1743
+ this.client = client;
1744
+ }
1745
+ async none(query, values) {
1746
+ await this.client.query(query, values);
1747
+ return null;
1748
+ }
1749
+ async one(query, values) {
1750
+ const result = await this.client.query(query, values);
1751
+ if (result.rows.length === 0) {
1752
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1753
+ }
1754
+ if (result.rows.length > 1) {
1755
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1756
+ }
1757
+ return result.rows[0];
1758
+ }
1759
+ async oneOrNone(query, values) {
1760
+ const result = await this.client.query(query, values);
1761
+ if (result.rows.length === 0) {
1762
+ return null;
1763
+ }
1764
+ if (result.rows.length > 1) {
1765
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1766
+ }
1767
+ return result.rows[0];
1768
+ }
1769
+ async any(query, values) {
1770
+ const result = await this.client.query(query, values);
1771
+ return result.rows;
1772
+ }
1773
+ async manyOrNone(query, values) {
1774
+ return this.any(query, values);
1775
+ }
1776
+ async many(query, values) {
1777
+ const result = await this.client.query(query, values);
1778
+ if (result.rows.length === 0) {
1779
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1780
+ }
1781
+ return result.rows;
1782
+ }
1783
+ async query(query, values) {
1784
+ return this.client.query(query, values);
1785
+ }
1786
+ async batch(promises) {
1787
+ return Promise.all(promises);
1788
+ }
1789
+ };
1664
1790
  function resolvePgConfig(config) {
1665
1791
  if ("client" in config) {
1666
1792
  return {
@@ -1670,10 +1796,32 @@ function resolvePgConfig(config) {
1670
1796
  indexes: config.indexes
1671
1797
  };
1672
1798
  }
1673
- const pgp = pgPromise();
1674
- const client = pgp(config);
1799
+ if ("pool" in config) {
1800
+ return {
1801
+ client: new PoolAdapter(config.pool),
1802
+ schemaName: config.schemaName,
1803
+ skipDefaultIndexes: config.skipDefaultIndexes,
1804
+ indexes: config.indexes
1805
+ };
1806
+ }
1807
+ let pool;
1808
+ if ("connectionString" in config) {
1809
+ pool = new Pool({
1810
+ connectionString: config.connectionString,
1811
+ ssl: config.ssl
1812
+ });
1813
+ } else {
1814
+ pool = new Pool({
1815
+ host: config.host,
1816
+ port: config.port,
1817
+ database: config.database,
1818
+ user: config.user,
1819
+ password: config.password,
1820
+ ssl: config.ssl
1821
+ });
1822
+ }
1675
1823
  return {
1676
- client,
1824
+ client: new PoolAdapter(pool),
1677
1825
  schemaName: config.schemaName,
1678
1826
  skipDefaultIndexes: config.skipDefaultIndexes,
1679
1827
  indexes: config.indexes
@@ -1688,6 +1836,87 @@ function getTableName({ indexName, schemaName }) {
1688
1836
  const quotedSchemaName = schemaName;
1689
1837
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1690
1838
  }
1839
+ function mapToSqlType(type) {
1840
+ switch (type) {
1841
+ case "uuid":
1842
+ return "UUID";
1843
+ case "boolean":
1844
+ return "BOOLEAN";
1845
+ default:
1846
+ return getSqlType(type);
1847
+ }
1848
+ }
1849
+ function generateTableSQL({
1850
+ tableName,
1851
+ schema,
1852
+ schemaName
1853
+ }) {
1854
+ const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
1855
+ const parsedName = parseSqlIdentifier(name, "column name");
1856
+ return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
1857
+ });
1858
+ const columns = Object.entries(schema).map(([name, def]) => {
1859
+ const parsedName = parseSqlIdentifier(name, "column name");
1860
+ const constraints = [];
1861
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
1862
+ if (!def.nullable) constraints.push("NOT NULL");
1863
+ return `"${parsedName}" ${mapToSqlType(def.type)} ${constraints.join(" ")}`;
1864
+ });
1865
+ const finalColumns = [...columns, ...timeZColumns].join(",\n");
1866
+ const parsedSchemaName = schemaName ? parseSqlIdentifier(schemaName, "schema name") : "";
1867
+ const constraintPrefix = parsedSchemaName ? `${parsedSchemaName}_` : "";
1868
+ const quotedSchemaName = getSchemaName(schemaName);
1869
+ const sql = `
1870
+ CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })} (
1871
+ ${finalColumns}
1872
+ );
1873
+ ${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
1874
+ DO $$ BEGIN
1875
+ IF NOT EXISTS (
1876
+ SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1877
+ ) AND NOT EXISTS (
1878
+ SELECT 1 FROM pg_indexes WHERE indexname = '${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key'
1879
+ ) THEN
1880
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1881
+ ADD CONSTRAINT ${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key
1882
+ UNIQUE (workflow_name, run_id);
1883
+ END IF;
1884
+ END $$;
1885
+ ` : ""}
1886
+ ${tableName === TABLE_SPANS ? `
1887
+ DO $$ BEGIN
1888
+ IF NOT EXISTS (
1889
+ SELECT 1 FROM pg_constraint WHERE conname = '${constraintPrefix}mastra_ai_spans_traceid_spanid_pk'
1890
+ ) THEN
1891
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: quotedSchemaName })}
1892
+ ADD CONSTRAINT ${constraintPrefix}mastra_ai_spans_traceid_spanid_pk
1893
+ PRIMARY KEY ("traceId", "spanId");
1894
+ END IF;
1895
+ END $$;
1896
+ ` : ""}
1897
+ `;
1898
+ return sql;
1899
+ }
1900
+ function exportSchemas(schemaName) {
1901
+ const statements = [];
1902
+ if (schemaName) {
1903
+ const quotedSchemaName = getSchemaName(schemaName);
1904
+ statements.push(`-- Create schema if it doesn't exist`);
1905
+ statements.push(`CREATE SCHEMA IF NOT EXISTS ${quotedSchemaName};`);
1906
+ statements.push("");
1907
+ }
1908
+ for (const [tableName, schema] of Object.entries(TABLE_SCHEMAS)) {
1909
+ statements.push(`-- Table: ${tableName}`);
1910
+ const sql = generateTableSQL({
1911
+ tableName,
1912
+ schema,
1913
+ schemaName
1914
+ });
1915
+ statements.push(sql.trim());
1916
+ statements.push("");
1917
+ }
1918
+ return statements.join("\n");
1919
+ }
1691
1920
  var schemaSetupRegistry = /* @__PURE__ */ new Map();
1692
1921
  var PgDB = class extends MastraBase {
1693
1922
  client;
@@ -1805,16 +2034,6 @@ var PgDB = class extends MastraBase {
1805
2034
  }
1806
2035
  await registryEntry.promise;
1807
2036
  }
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
2037
  getDefaultValue(type) {
1819
2038
  switch (type) {
1820
2039
  case "timestamp":
@@ -1884,52 +2103,10 @@ var PgDB = class extends MastraBase {
1884
2103
  }) {
1885
2104
  try {
1886
2105
  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
2106
  if (this.schemaName) {
1899
2107
  await this.setupSchema();
1900
2108
  }
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
- `;
2109
+ const sql = generateTableSQL({ tableName, schema, schemaName: this.schemaName });
1933
2110
  await this.client.none(sql);
1934
2111
  await this.alterTable({
1935
2112
  tableName,
@@ -2003,7 +2180,7 @@ var PgDB = class extends MastraBase {
2003
2180
  const columnExists = await this.hasColumn(TABLE_SPANS, columnName);
2004
2181
  if (!columnExists) {
2005
2182
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
2006
- const sqlType = this.getSqlType(columnDef.type);
2183
+ const sqlType = mapToSqlType(columnDef.type);
2007
2184
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2008
2185
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2009
2186
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
@@ -2050,7 +2227,7 @@ var PgDB = class extends MastraBase {
2050
2227
  if (schema[columnName]) {
2051
2228
  const columnDef = schema[columnName];
2052
2229
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
2053
- const sqlType = this.getSqlType(columnDef.type);
2230
+ const sqlType = mapToSqlType(columnDef.type);
2054
2231
  const nullable = columnDef.nullable ? "" : "NOT NULL";
2055
2232
  const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
2056
2233
  const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
@@ -2370,9 +2547,9 @@ var PgDB = class extends MastraBase {
2370
2547
  size: result.size || "0",
2371
2548
  definition: result.definition || "",
2372
2549
  method: result.method || "btree",
2373
- scans: parseInt(result.scans) || 0,
2374
- tuples_read: parseInt(result.tuples_read) || 0,
2375
- tuples_fetched: parseInt(result.tuples_fetched) || 0
2550
+ scans: parseInt(String(result.scans)) || 0,
2551
+ tuples_read: parseInt(String(result.tuples_read)) || 0,
2552
+ tuples_fetched: parseInt(String(result.tuples_fetched)) || 0
2376
2553
  };
2377
2554
  } catch (error) {
2378
2555
  throw new MastraError(
@@ -2889,6 +3066,9 @@ function getTableName3({ indexName, schemaName }) {
2889
3066
  const quotedIndexName = `"${indexName}"`;
2890
3067
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
2891
3068
  }
3069
+ function inPlaceholders(count, startIndex = 1) {
3070
+ return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
3071
+ }
2892
3072
  var MemoryPG = class _MemoryPG extends MemoryStorage {
2893
3073
  #db;
2894
3074
  #schema;
@@ -3049,13 +3229,19 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3049
3229
  };
3050
3230
  }
3051
3231
  const limitValue = perPageInput === false ? total : perPage;
3052
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`;
3053
- const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
3232
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT $2 OFFSET $3`;
3233
+ const rows = await this.#db.client.manyOrNone(
3234
+ dataQuery,
3235
+ [...queryParams, limitValue, offset]
3236
+ );
3054
3237
  const threads = (rows || []).map((thread) => ({
3055
- ...thread,
3238
+ id: thread.id,
3239
+ resourceId: thread.resourceId,
3240
+ title: thread.title,
3056
3241
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
3057
- createdAt: thread.createdAt,
3058
- updatedAt: thread.updatedAt
3242
+ // Use timezone-aware columns (*Z) for correct UTC timestamps, with fallback for legacy data
3243
+ createdAt: thread.createdAtZ || thread.createdAt,
3244
+ updatedAt: thread.updatedAtZ || thread.updatedAt
3059
3245
  }));
3060
3246
  return {
3061
3247
  threads,
@@ -3160,17 +3346,18 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3160
3346
  ...metadata
3161
3347
  };
3162
3348
  try {
3349
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3163
3350
  const thread = await this.#db.client.one(
3164
3351
  `UPDATE ${threadTableName}
3165
3352
  SET
3166
3353
  title = $1,
3167
3354
  metadata = $2,
3168
3355
  "updatedAt" = $3,
3169
- "updatedAtZ" = $3
3170
- WHERE id = $4
3356
+ "updatedAtZ" = $4
3357
+ WHERE id = $5
3171
3358
  RETURNING *
3172
3359
  `,
3173
- [title, mergedMetadata, (/* @__PURE__ */ new Date()).toISOString(), id]
3360
+ [title, mergedMetadata, now, now, id]
3174
3361
  );
3175
3362
  return {
3176
3363
  id: thread.id,
@@ -3313,7 +3500,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3313
3500
  const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3314
3501
  const query = `
3315
3502
  ${selectStatement} FROM ${tableName}
3316
- WHERE id IN (${messageIds.map((_, i) => `$${i + 1}`).join(", ")})
3503
+ WHERE id IN (${inPlaceholders(messageIds.length)})
3317
3504
  ORDER BY "createdAt" DESC
3318
3505
  `;
3319
3506
  const resultRows = await this.#db.client.manyOrNone(query, messageIds);
@@ -3374,8 +3561,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3374
3561
  const orderByStatement = `ORDER BY "${field}" ${direction}`;
3375
3562
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
3376
3563
  const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3377
- const threadPlaceholders = threadIds.map((_, i) => `$${i + 1}`).join(", ");
3378
- const conditions = [`thread_id IN (${threadPlaceholders})`];
3564
+ const conditions = [`thread_id IN (${inPlaceholders(threadIds.length)})`];
3379
3565
  const queryParams = [...threadIds];
3380
3566
  let paramIndex = threadIds.length + 1;
3381
3567
  if (resourceId) {
@@ -3383,11 +3569,13 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3383
3569
  queryParams.push(resourceId);
3384
3570
  }
3385
3571
  if (filter?.dateRange?.start) {
3386
- conditions.push(`"createdAt" >= $${paramIndex++}`);
3572
+ const startOp = filter.dateRange.startExclusive ? ">" : ">=";
3573
+ conditions.push(`"createdAt" ${startOp} $${paramIndex++}`);
3387
3574
  queryParams.push(filter.dateRange.start);
3388
3575
  }
3389
3576
  if (filter?.dateRange?.end) {
3390
- conditions.push(`"createdAt" <= $${paramIndex++}`);
3577
+ const endOp = filter.dateRange.endExclusive ? "<" : "<=";
3578
+ conditions.push(`"createdAt" ${endOp} $${paramIndex++}`);
3391
3579
  queryParams.push(filter.dateRange.end);
3392
3580
  }
3393
3581
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
@@ -3532,14 +3720,15 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3532
3720
  );
3533
3721
  });
3534
3722
  const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
3723
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
3535
3724
  const threadUpdate = t.none(
3536
3725
  `UPDATE ${threadTableName}
3537
3726
  SET
3538
3727
  "updatedAt" = $1,
3539
- "updatedAtZ" = $1
3540
- WHERE id = $2
3728
+ "updatedAtZ" = $2
3729
+ WHERE id = $3
3541
3730
  `,
3542
- [(/* @__PURE__ */ new Date()).toISOString(), threadId]
3731
+ [nowStr, nowStr, threadId]
3543
3732
  );
3544
3733
  await Promise.all([...messageInserts, threadUpdate]);
3545
3734
  });
@@ -3576,8 +3765,8 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3576
3765
  return [];
3577
3766
  }
3578
3767
  const messageIds = messages.map((m) => m.id);
3579
- 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)`;
3580
- const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
3768
+ 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)})`;
3769
+ const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, messageIds);
3581
3770
  if (existingMessagesDb.length === 0) {
3582
3771
  return [];
3583
3772
  }
@@ -3638,10 +3827,11 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3638
3827
  }
3639
3828
  }
3640
3829
  if (threadIdsToUpdate.size > 0) {
3830
+ const threadIds = Array.from(threadIdsToUpdate);
3641
3831
  queries.push(
3642
3832
  t.none(
3643
- `UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN ($1:list)`,
3644
- [Array.from(threadIdsToUpdate)]
3833
+ `UPDATE ${getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`,
3834
+ threadIds
3645
3835
  )
3646
3836
  );
3647
3837
  }
@@ -3649,7 +3839,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3649
3839
  await t.batch(queries);
3650
3840
  }
3651
3841
  });
3652
- const updatedMessages = await this.#db.client.manyOrNone(selectQuery, [messageIds]);
3842
+ const updatedMessages = await this.#db.client.manyOrNone(selectQuery, messageIds);
3653
3843
  return (updatedMessages || []).map((row) => {
3654
3844
  const message = this.normalizeMessageRow(row);
3655
3845
  if (typeof message.content === "string") {
@@ -3761,15 +3951,159 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
3761
3951
  values.push(JSON.stringify(updatedResource.metadata));
3762
3952
  paramIndex++;
3763
3953
  }
3764
- updates.push(`"updatedAt" = $${paramIndex}`);
3765
- values.push(updatedResource.updatedAt.toISOString());
3954
+ const updatedAtStr = updatedResource.updatedAt.toISOString();
3955
+ updates.push(`"updatedAt" = $${paramIndex++}`);
3956
+ values.push(updatedAtStr);
3766
3957
  updates.push(`"updatedAtZ" = $${paramIndex++}`);
3767
- values.push(updatedResource.updatedAt.toISOString());
3768
- paramIndex++;
3958
+ values.push(updatedAtStr);
3769
3959
  values.push(resourceId);
3770
3960
  await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
3771
3961
  return updatedResource;
3772
3962
  }
3963
+ async cloneThread(args) {
3964
+ const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
3965
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
3966
+ if (!sourceThread) {
3967
+ throw new MastraError({
3968
+ id: createStorageErrorId("PG", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
3969
+ domain: ErrorDomain.STORAGE,
3970
+ category: ErrorCategory.USER,
3971
+ text: `Source thread with id ${sourceThreadId} not found`,
3972
+ details: { sourceThreadId }
3973
+ });
3974
+ }
3975
+ const newThreadId = providedThreadId || crypto.randomUUID();
3976
+ const existingThread = await this.getThreadById({ threadId: newThreadId });
3977
+ if (existingThread) {
3978
+ throw new MastraError({
3979
+ id: createStorageErrorId("PG", "CLONE_THREAD", "THREAD_EXISTS"),
3980
+ domain: ErrorDomain.STORAGE,
3981
+ category: ErrorCategory.USER,
3982
+ text: `Thread with id ${newThreadId} already exists`,
3983
+ details: { newThreadId }
3984
+ });
3985
+ }
3986
+ const threadTableName = getTableName3({ indexName: TABLE_THREADS, schemaName: getSchemaName3(this.#schema) });
3987
+ const messageTableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
3988
+ try {
3989
+ return await this.#db.client.tx(async (t) => {
3990
+ let messageQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"
3991
+ FROM ${messageTableName} WHERE thread_id = $1`;
3992
+ const messageParams = [sourceThreadId];
3993
+ let paramIndex = 2;
3994
+ if (options?.messageFilter?.startDate) {
3995
+ messageQuery += ` AND "createdAt" >= $${paramIndex++}`;
3996
+ messageParams.push(options.messageFilter.startDate);
3997
+ }
3998
+ if (options?.messageFilter?.endDate) {
3999
+ messageQuery += ` AND "createdAt" <= $${paramIndex++}`;
4000
+ messageParams.push(options.messageFilter.endDate);
4001
+ }
4002
+ if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
4003
+ messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => `$${paramIndex++}`).join(", ")})`;
4004
+ messageParams.push(...options.messageFilter.messageIds);
4005
+ }
4006
+ messageQuery += ` ORDER BY "createdAt" ASC`;
4007
+ if (options?.messageLimit && options.messageLimit > 0) {
4008
+ const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT $${paramIndex}) AS limited ORDER BY "createdAt" ASC`;
4009
+ messageParams.push(options.messageLimit);
4010
+ messageQuery = limitQuery;
4011
+ }
4012
+ const sourceMessages = await t.manyOrNone(messageQuery, messageParams);
4013
+ const now = /* @__PURE__ */ new Date();
4014
+ const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
4015
+ const cloneMetadata = {
4016
+ sourceThreadId,
4017
+ clonedAt: now,
4018
+ ...lastMessageId && { lastMessageId }
4019
+ };
4020
+ const newThread = {
4021
+ id: newThreadId,
4022
+ resourceId: resourceId || sourceThread.resourceId,
4023
+ title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
4024
+ metadata: {
4025
+ ...metadata,
4026
+ clone: cloneMetadata
4027
+ },
4028
+ createdAt: now,
4029
+ updatedAt: now
4030
+ };
4031
+ await t.none(
4032
+ `INSERT INTO ${threadTableName} (
4033
+ id,
4034
+ "resourceId",
4035
+ title,
4036
+ metadata,
4037
+ "createdAt",
4038
+ "createdAtZ",
4039
+ "updatedAt",
4040
+ "updatedAtZ"
4041
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4042
+ [
4043
+ newThread.id,
4044
+ newThread.resourceId,
4045
+ newThread.title,
4046
+ newThread.metadata ? JSON.stringify(newThread.metadata) : null,
4047
+ now,
4048
+ now,
4049
+ now,
4050
+ now
4051
+ ]
4052
+ );
4053
+ const clonedMessages = [];
4054
+ const targetResourceId = resourceId || sourceThread.resourceId;
4055
+ for (const sourceMsg of sourceMessages) {
4056
+ const newMessageId = crypto.randomUUID();
4057
+ const normalizedMsg = this.normalizeMessageRow(sourceMsg);
4058
+ let parsedContent = normalizedMsg.content;
4059
+ try {
4060
+ parsedContent = JSON.parse(normalizedMsg.content);
4061
+ } catch {
4062
+ }
4063
+ await t.none(
4064
+ `INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
4065
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
4066
+ [
4067
+ newMessageId,
4068
+ newThreadId,
4069
+ typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
4070
+ normalizedMsg.createdAt,
4071
+ normalizedMsg.createdAt,
4072
+ normalizedMsg.role,
4073
+ normalizedMsg.type || "v2",
4074
+ targetResourceId
4075
+ ]
4076
+ );
4077
+ clonedMessages.push({
4078
+ id: newMessageId,
4079
+ threadId: newThreadId,
4080
+ content: parsedContent,
4081
+ role: normalizedMsg.role,
4082
+ type: normalizedMsg.type,
4083
+ createdAt: new Date(normalizedMsg.createdAt),
4084
+ resourceId: targetResourceId
4085
+ });
4086
+ }
4087
+ return {
4088
+ thread: newThread,
4089
+ clonedMessages
4090
+ };
4091
+ });
4092
+ } catch (error) {
4093
+ if (error instanceof MastraError) {
4094
+ throw error;
4095
+ }
4096
+ throw new MastraError(
4097
+ {
4098
+ id: createStorageErrorId("PG", "CLONE_THREAD", "FAILED"),
4099
+ domain: ErrorDomain.STORAGE,
4100
+ category: ErrorCategory.THIRD_PARTY,
4101
+ details: { sourceThreadId, newThreadId }
4102
+ },
4103
+ error
4104
+ );
4105
+ }
4106
+ }
3773
4107
  };
3774
4108
  var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
3775
4109
  #db;
@@ -5044,9 +5378,12 @@ var WorkflowsPG = class _WorkflowsPG extends WorkflowsStorage {
5044
5378
  };
5045
5379
 
5046
5380
  // src/storage/index.ts
5381
+ var DEFAULT_MAX_CONNECTIONS = 20;
5382
+ var DEFAULT_IDLE_TIMEOUT_MS = 3e4;
5047
5383
  var PostgresStore = class extends MastraStorage {
5384
+ #pool;
5048
5385
  #db;
5049
- #pgp;
5386
+ #ownsPool;
5050
5387
  schema;
5051
5388
  isInitialized = false;
5052
5389
  stores;
@@ -5055,59 +5392,26 @@ var PostgresStore = class extends MastraStorage {
5055
5392
  validateConfig("PostgresStore", config);
5056
5393
  super({ id: config.id, name: "PostgresStore", disableInit: config.disableInit });
5057
5394
  this.schema = config.schemaName || "public";
5058
- this.#pgp = pgPromise();
5059
- if (isClientConfig(config)) {
5060
- this.#db = config.client;
5395
+ if (isPoolConfig(config)) {
5396
+ this.#pool = config.pool;
5397
+ this.#ownsPool = false;
5061
5398
  } else {
5062
- let pgConfig;
5063
- if (isConnectionStringConfig(config)) {
5064
- pgConfig = {
5065
- id: config.id,
5066
- connectionString: config.connectionString,
5067
- max: config.max,
5068
- idleTimeoutMillis: config.idleTimeoutMillis,
5069
- ssl: config.ssl
5070
- };
5071
- } else if (isCloudSqlConfig(config)) {
5072
- pgConfig = {
5073
- ...config,
5074
- id: config.id,
5075
- max: config.max,
5076
- idleTimeoutMillis: config.idleTimeoutMillis
5077
- };
5078
- } else if (isHostConfig(config)) {
5079
- pgConfig = {
5080
- id: config.id,
5081
- host: config.host,
5082
- port: config.port,
5083
- database: config.database,
5084
- user: config.user,
5085
- password: config.password,
5086
- ssl: config.ssl,
5087
- max: config.max,
5088
- idleTimeoutMillis: config.idleTimeoutMillis
5089
- };
5090
- } else {
5091
- throw new Error(
5092
- "PostgresStore: invalid config. Provide either {client}, {connectionString}, {host,port,database,user,password}, or a pg ClientConfig (e.g., Cloud SQL connector with `stream`)."
5093
- );
5094
- }
5095
- this.#db = this.#pgp(pgConfig);
5096
- }
5097
- const skipDefaultIndexes = config.skipDefaultIndexes;
5098
- const indexes = config.indexes;
5099
- const domainConfig = { client: this.#db, schemaName: this.schema, skipDefaultIndexes, indexes };
5100
- const scores = new ScoresPG(domainConfig);
5101
- const workflows = new WorkflowsPG(domainConfig);
5102
- const memory = new MemoryPG(domainConfig);
5103
- const observability = new ObservabilityPG(domainConfig);
5104
- const agents = new AgentsPG(domainConfig);
5399
+ this.#pool = this.createPool(config);
5400
+ this.#ownsPool = true;
5401
+ }
5402
+ this.#db = new PoolAdapter(this.#pool);
5403
+ const domainConfig = {
5404
+ client: this.#db,
5405
+ schemaName: this.schema,
5406
+ skipDefaultIndexes: config.skipDefaultIndexes,
5407
+ indexes: config.indexes
5408
+ };
5105
5409
  this.stores = {
5106
- scores,
5107
- workflows,
5108
- memory,
5109
- observability,
5110
- agents
5410
+ scores: new ScoresPG(domainConfig),
5411
+ workflows: new WorkflowsPG(domainConfig),
5412
+ memory: new MemoryPG(domainConfig),
5413
+ observability: new ObservabilityPG(domainConfig),
5414
+ agents: new AgentsPG(domainConfig)
5111
5415
  };
5112
5416
  } catch (e) {
5113
5417
  throw new MastraError(
@@ -5120,6 +5424,32 @@ var PostgresStore = class extends MastraStorage {
5120
5424
  );
5121
5425
  }
5122
5426
  }
5427
+ createPool(config) {
5428
+ if (isConnectionStringConfig(config)) {
5429
+ return new Pool({
5430
+ connectionString: config.connectionString,
5431
+ ssl: config.ssl,
5432
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
5433
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
5434
+ });
5435
+ }
5436
+ if (isHostConfig(config)) {
5437
+ return new Pool({
5438
+ host: config.host,
5439
+ port: config.port,
5440
+ database: config.database,
5441
+ user: config.user,
5442
+ password: config.password,
5443
+ ssl: config.ssl,
5444
+ max: config.max ?? DEFAULT_MAX_CONNECTIONS,
5445
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DEFAULT_IDLE_TIMEOUT_MS
5446
+ });
5447
+ }
5448
+ if (isCloudSqlConfig(config)) {
5449
+ return new Pool(config);
5450
+ }
5451
+ throw new Error("PostgresStore: invalid config");
5452
+ }
5123
5453
  async init() {
5124
5454
  if (this.isInitialized) {
5125
5455
  return;
@@ -5139,19 +5469,32 @@ var PostgresStore = class extends MastraStorage {
5139
5469
  );
5140
5470
  }
5141
5471
  }
5472
+ /**
5473
+ * Database client for executing queries.
5474
+ *
5475
+ * @example
5476
+ * ```typescript
5477
+ * const rows = await store.db.any('SELECT * FROM users WHERE active = $1', [true]);
5478
+ * const user = await store.db.one('SELECT * FROM users WHERE id = $1', [userId]);
5479
+ * ```
5480
+ */
5142
5481
  get db() {
5143
5482
  return this.#db;
5144
5483
  }
5145
- get pgp() {
5146
- return this.#pgp;
5484
+ /**
5485
+ * The underlying pg.Pool for direct database access or ORM integration.
5486
+ */
5487
+ get pool() {
5488
+ return this.#pool;
5147
5489
  }
5148
5490
  /**
5149
- * Closes the pg-promise connection pool.
5150
- *
5151
- * This will close ALL connections in the pool, including pre-configured clients.
5491
+ * Closes the connection pool if it was created by this store.
5492
+ * If a pool was passed in via config, it will not be closed.
5152
5493
  */
5153
5494
  async close() {
5154
- this.pgp.end();
5495
+ if (this.#ownsPool) {
5496
+ await this.#pool.end();
5497
+ }
5155
5498
  }
5156
5499
  };
5157
5500
 
@@ -5254,6 +5597,6 @@ Example Complex Query:
5254
5597
  ]
5255
5598
  }`;
5256
5599
 
5257
- export { AgentsPG, MemoryPG, ObservabilityPG, PGVECTOR_PROMPT, PgVector, PostgresStore, ScoresPG, WorkflowsPG };
5600
+ export { AgentsPG, MemoryPG, ObservabilityPG, PGVECTOR_PROMPT, PgVector, PoolAdapter, PostgresStore, ScoresPG, WorkflowsPG, exportSchemas };
5258
5601
  //# sourceMappingURL=index.js.map
5259
5602
  //# sourceMappingURL=index.js.map