@mastra/pg 1.19.0 → 1.20.0-alpha.1

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
@@ -146,13 +146,23 @@ var PGFilterTranslator = class extends BaseFilterTranslator {
146
146
  };
147
147
  //#endregion
148
148
  //#region src/vector/sql-builder.ts
149
+ const getTextExtractExpr = (key) => {
150
+ const jsonPathKey = parseJsonPathKey(key);
151
+ if (!key.includes(".")) return `metadata->>'${jsonPathKey}'`;
152
+ return `metadata#>>'{${jsonPathKey}}'`;
153
+ };
154
+ const getJsonExtractExpr = (key) => {
155
+ const jsonPathKey = parseJsonPathKey(key);
156
+ if (!key.includes(".")) return `metadata->'${jsonPathKey}'`;
157
+ return `metadata#>'{${jsonPathKey}}'`;
158
+ };
149
159
  const createBasicOperator = (symbol) => {
150
160
  return (key, paramIndex) => {
151
- const jsonPathKey = parseJsonPathKey(key);
161
+ const textExtract = getTextExtractExpr(key);
152
162
  return {
153
- sql: `CASE
154
- WHEN $${paramIndex}::text IS NULL THEN metadata#>>'{${jsonPathKey}}' IS ${symbol === "=" ? "" : "NOT"} NULL
155
- ELSE metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text
163
+ sql: `CASE
164
+ WHEN $${paramIndex}::text IS NULL THEN ${textExtract} IS ${symbol === "=" ? "" : "NOT"} NULL
165
+ ELSE ${textExtract} ${symbol} $${paramIndex}::text
156
166
  END`,
157
167
  needsValue: true
158
168
  };
@@ -160,13 +170,14 @@ const createBasicOperator = (symbol) => {
160
170
  };
161
171
  const createNumericOperator = (symbol) => {
162
172
  return (key, paramIndex, value) => {
163
- const jsonPathKey = parseJsonPathKey(key);
173
+ const textExtract = getTextExtractExpr(key);
174
+ const jsonExtract = getJsonExtractExpr(key);
164
175
  if (typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "") return {
165
- sql: `(CASE WHEN jsonb_typeof(metadata#>'{${jsonPathKey}}') = 'number' THEN (metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}::numeric ELSE NULL END)`,
176
+ sql: `(CASE WHEN jsonb_typeof(${jsonExtract}) = 'number' THEN (${textExtract})::numeric ${symbol} $${paramIndex}::numeric ELSE NULL END)`,
166
177
  needsValue: true
167
178
  };
168
179
  else return {
169
- sql: `metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text`,
180
+ sql: `${textExtract} ${symbol} $${paramIndex}::text`,
170
181
  needsValue: true
171
182
  };
172
183
  };
@@ -197,7 +208,7 @@ function buildElemMatchConditions(value, paramIndex) {
197
208
  const operatorFn = FILTER_OPERATORS[paramOperator];
198
209
  if (!operatorFn) throw new Error(`Invalid operator: ${paramOperator}`);
199
210
  const result = operatorFn(paramKey, nextParamIndex, paramValue);
200
- const sql = result.sql.replaceAll("metadata#>>", "elem#>>").replaceAll("metadata#>", "elem#>");
211
+ const sql = result.sql.replaceAll("metadata->>", "elem->>").replaceAll("metadata->", "elem->").replaceAll("metadata#>>", "elem#>>").replaceAll("metadata#>", "elem#>");
201
212
  conditions.push(sql);
202
213
  if (result.needsValue) values.push(paramValue);
203
214
  });
@@ -214,32 +225,34 @@ const FILTER_OPERATORS = {
214
225
  $lt: createNumericOperator("<"),
215
226
  $lte: createNumericOperator("<="),
216
227
  $in: (key, paramIndex) => {
217
- const jsonPathKey = parseJsonPathKey(key);
228
+ const textExtract = getTextExtractExpr(key);
229
+ const jsonExtract = getJsonExtractExpr(key);
218
230
  return {
219
231
  sql: `(
220
232
  CASE
221
- WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
233
+ WHEN jsonb_typeof(${jsonExtract}) = 'array' THEN
222
234
  EXISTS (
223
- SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
235
+ SELECT 1 FROM jsonb_array_elements_text(${jsonExtract}) as elem
224
236
  WHERE elem = ANY($${paramIndex}::text[])
225
237
  )
226
- ELSE metadata#>>'{${jsonPathKey}}' = ANY($${paramIndex}::text[])
238
+ ELSE ${textExtract} = ANY($${paramIndex}::text[])
227
239
  END
228
240
  )`,
229
241
  needsValue: true
230
242
  };
231
243
  },
232
244
  $nin: (key, paramIndex) => {
233
- const jsonPathKey = parseJsonPathKey(key);
245
+ const textExtract = getTextExtractExpr(key);
246
+ const jsonExtract = getJsonExtractExpr(key);
234
247
  return {
235
248
  sql: `(
236
249
  CASE
237
- WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
250
+ WHEN jsonb_typeof(${jsonExtract}) = 'array' THEN
238
251
  NOT EXISTS (
239
- SELECT 1 FROM jsonb_array_elements_text(metadata->'${jsonPathKey}') as elem
252
+ SELECT 1 FROM jsonb_array_elements_text(${jsonExtract}) as elem
240
253
  WHERE elem = ANY($${paramIndex}::text[])
241
254
  )
242
- ELSE metadata#>>'{${jsonPathKey}}' != ALL($${paramIndex}::text[])
255
+ ELSE ${textExtract} != ALL($${paramIndex}::text[])
243
256
  END
244
257
  )`,
245
258
  needsValue: true
@@ -247,21 +260,21 @@ const FILTER_OPERATORS = {
247
260
  },
248
261
  $all: (key, paramIndex) => {
249
262
  return {
250
- sql: `CASE WHEN array_length($${paramIndex}::text[], 1) IS NULL THEN false
251
- ELSE (metadata#>'{${parseJsonPathKey(key)}}')::jsonb ?& $${paramIndex}::text[] END`,
263
+ sql: `CASE WHEN array_length($${paramIndex}::text[], 1) IS NULL THEN false
264
+ ELSE (${getJsonExtractExpr(key)})::jsonb ?& $${paramIndex}::text[] END`,
252
265
  needsValue: true
253
266
  };
254
267
  },
255
268
  $elemMatch: (key, paramIndex, value) => {
256
269
  const { sql, values } = buildElemMatchConditions(value, paramIndex);
257
- const jsonPathKey = parseJsonPathKey(key);
270
+ const jsonExtract = getJsonExtractExpr(key);
258
271
  return {
259
272
  sql: `(
260
273
  CASE
261
- WHEN jsonb_typeof(metadata->'${jsonPathKey}') = 'array' THEN
274
+ WHEN jsonb_typeof(${jsonExtract}) = 'array' THEN
262
275
  EXISTS (
263
- SELECT 1
264
- FROM jsonb_array_elements(metadata->'${jsonPathKey}') as elem
276
+ SELECT 1
277
+ FROM jsonb_array_elements(${jsonExtract}) as elem
265
278
  WHERE ${sql}
266
279
  )
267
280
  ELSE FALSE
@@ -291,7 +304,7 @@ const FILTER_OPERATORS = {
291
304
  needsValue: false
292
305
  }),
293
306
  $not: (key) => ({
294
- sql: `NOT (${key})`,
307
+ sql: `(${key})`,
295
308
  needsValue: false
296
309
  }),
297
310
  $nor: (key) => ({
@@ -300,16 +313,17 @@ const FILTER_OPERATORS = {
300
313
  }),
301
314
  $regex: (key, paramIndex) => {
302
315
  return {
303
- sql: `metadata#>>'{${parseJsonPathKey(key)}}' ~ $${paramIndex}`,
316
+ sql: `${getTextExtractExpr(key)} ~ $${paramIndex}`,
304
317
  needsValue: true
305
318
  };
306
319
  },
307
320
  $contains: (key, paramIndex, value) => {
308
- const jsonPathKey = parseJsonPathKey(key);
321
+ const textExtract = getTextExtractExpr(key);
322
+ const jsonExtract = getJsonExtractExpr(key);
309
323
  let sql;
310
- if (Array.isArray(value)) sql = `(metadata->'${jsonPathKey}') ?& $${paramIndex}`;
311
- else if (typeof value === "string") sql = `metadata->>'${jsonPathKey}' ILIKE '%' || $${paramIndex} || '%' ESCAPE '\\'`;
312
- else sql = `metadata->>'${jsonPathKey}' = $${paramIndex}`;
324
+ if (Array.isArray(value)) sql = `(${jsonExtract}) ?& $${paramIndex}`;
325
+ else if (typeof value === "string") sql = `${textExtract} ILIKE '%' || $${paramIndex} || '%' ESCAPE '\\'`;
326
+ else sql = `${textExtract} = $${paramIndex}`;
313
327
  return {
314
328
  sql,
315
329
  needsValue: true,
@@ -321,12 +335,12 @@ const FILTER_OPERATORS = {
321
335
  * Usage: { field: { $objectContains: { ...subobject } } }
322
336
  */
323
337
  $size: (key, paramIndex) => {
324
- const jsonPathKey = parseJsonPathKey(key);
338
+ const jsonExtract = getJsonExtractExpr(key);
325
339
  return {
326
340
  sql: `(
327
341
  CASE
328
- WHEN jsonb_typeof(metadata#>'{${jsonPathKey}}') = 'array' THEN
329
- jsonb_array_length(metadata#>'{${jsonPathKey}}') = $${paramIndex}
342
+ WHEN jsonb_typeof(${jsonExtract}) = 'array' THEN
343
+ jsonb_array_length(${jsonExtract}) = $${paramIndex}
330
344
  ELSE FALSE
331
345
  END
332
346
  )`,
@@ -354,7 +368,7 @@ function buildDeleteFilterQuery(filter) {
354
368
  ].includes(key)) return handleLogicalOperator(key, value, parentPath);
355
369
  if (!value || typeof value !== "object") {
356
370
  values.push(value);
357
- return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
371
+ return `${getTextExtractExpr(key)} = $${values.length}`;
358
372
  }
359
373
  const entries = Object.entries(value);
360
374
  if (entries.length > 1) return entries.map(([operator, operatorValue]) => {
@@ -445,7 +459,7 @@ function buildFilterQuery(filter, minScore, topK) {
445
459
  ].includes(key)) return handleLogicalOperator(key, value, parentPath);
446
460
  if (!value || typeof value !== "object") {
447
461
  values.push(value);
448
- return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
462
+ return `${getTextExtractExpr(key)} = $${values.length}`;
449
463
  }
450
464
  const entries = Object.entries(value);
451
465
  if (entries.length > 1) return entries.map(([operator, operatorValue]) => {
@@ -1778,16 +1792,21 @@ var PoolAdapter = class {
1778
1792
  const client = await this.$pool.connect();
1779
1793
  try {
1780
1794
  await client.query("BEGIN");
1781
- const result = await callback(new TransactionClient(client));
1782
- await client.query("COMMIT");
1783
- return result;
1784
- } catch (error) {
1795
+ const txClient = new TransactionClient(client);
1785
1796
  try {
1786
- await client.query("ROLLBACK");
1787
- } catch (rollbackError) {
1788
- console.error("Transaction rollback failed:", rollbackError);
1797
+ const result = await callback(txClient);
1798
+ await txClient.drain();
1799
+ await client.query("COMMIT");
1800
+ return result;
1801
+ } catch (error) {
1802
+ await txClient.drain().catch(() => void 0);
1803
+ try {
1804
+ await client.query("ROLLBACK");
1805
+ } catch (rollbackError) {
1806
+ console.error("Transaction rollback failed:", rollbackError);
1807
+ }
1808
+ throw error;
1789
1809
  }
1790
- throw error;
1791
1810
  } finally {
1792
1811
  client.release();
1793
1812
  }
@@ -1795,41 +1814,85 @@ var PoolAdapter = class {
1795
1814
  };
1796
1815
  /**
1797
1816
  * Transaction client that wraps a PoolClient for executing queries within a transaction.
1817
+ *
1818
+ * Query methods are serialized through a tail promise (same pattern as
1819
+ * PinnedClientAdapter). Callers such as memory.updateMessages historically
1820
+ * did `queries.push(t.none(...))` then `await t.batch(queries)` — each
1821
+ * `t.none()` is async and starts `client.query` immediately, so by the time
1822
+ * batch runs, N queries are already in flight on one PoolClient. pg@8 queues
1823
+ * those internally and emits a DeprecationWarning; pg@9 will throw.
1824
+ * (#20820)
1798
1825
  */
1799
1826
  var TransactionClient = class {
1800
1827
  client;
1828
+ /**
1829
+ * Serialization tail. Without this gate, concurrent t.none()/t.query()
1830
+ * from Promise.all / batch land on the same PoolClient at once.
1831
+ */
1832
+ #tail = Promise.resolve();
1833
+ #error;
1801
1834
  constructor(client) {
1802
1835
  this.client = client;
1803
1836
  }
1804
- async none(query, values) {
1805
- await this.client.query(query, values);
1806
- return null;
1837
+ #enqueue(fn) {
1838
+ const next = this.#tail.then(fn);
1839
+ this.#tail = next.then(() => void 0, (error) => {
1840
+ this.#error ??= { value: error };
1841
+ });
1842
+ return next;
1807
1843
  }
1808
- async one(query, values) {
1809
- const result = await this.client.query(query, values);
1810
- if (result.rows.length === 0) throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1811
- if (result.rows.length > 1) throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1812
- return result.rows[0];
1844
+ /**
1845
+ * Wait until every enqueued query has settled and surface the first failure.
1846
+ * PoolAdapter / PinnedClientAdapter call this before COMMIT/ROLLBACK so
1847
+ * those control statements never overlap in-flight work on the same client.
1848
+ */
1849
+ async drain() {
1850
+ await this.#tail;
1851
+ if (this.#error) {
1852
+ const { value } = this.#error;
1853
+ this.#error = void 0;
1854
+ throw value;
1855
+ }
1813
1856
  }
1814
- async oneOrNone(query, values) {
1815
- const result = await this.client.query(query, values);
1816
- if (result.rows.length === 0) return null;
1817
- if (result.rows.length > 1) throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1818
- return result.rows[0];
1857
+ none(query, values) {
1858
+ return this.#enqueue(async () => {
1859
+ await this.client.query(query, values);
1860
+ return null;
1861
+ });
1819
1862
  }
1820
- async any(query, values) {
1821
- return (await this.client.query(query, values)).rows;
1863
+ one(query, values) {
1864
+ return this.#enqueue(async () => {
1865
+ const result = await this.client.query(query, values);
1866
+ if (result.rows.length === 0) throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1867
+ if (result.rows.length > 1) throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
1868
+ return result.rows[0];
1869
+ });
1822
1870
  }
1823
- async manyOrNone(query, values) {
1871
+ oneOrNone(query, values) {
1872
+ return this.#enqueue(async () => {
1873
+ const result = await this.client.query(query, values);
1874
+ if (result.rows.length === 0) return null;
1875
+ if (result.rows.length > 1) throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
1876
+ return result.rows[0];
1877
+ });
1878
+ }
1879
+ any(query, values) {
1880
+ return this.#enqueue(async () => {
1881
+ return (await this.client.query(query, values)).rows;
1882
+ });
1883
+ }
1884
+ manyOrNone(query, values) {
1824
1885
  return this.any(query, values);
1825
1886
  }
1826
- async many(query, values) {
1827
- const result = await this.client.query(query, values);
1828
- if (result.rows.length === 0) throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1829
- return result.rows;
1887
+ many(query, values) {
1888
+ return this.#enqueue(async () => {
1889
+ const result = await this.client.query(query, values);
1890
+ if (result.rows.length === 0) throw new Error(`No data returned from query: ${truncateQuery(query)}`);
1891
+ return result.rows;
1892
+ });
1830
1893
  }
1831
- async query(query, values) {
1832
- return this.client.query(query, values);
1894
+ query(query, values) {
1895
+ return this.#enqueue(() => this.client.query(query, values));
1833
1896
  }
1834
1897
  async batch(promises) {
1835
1898
  return Promise.all(promises);
@@ -1922,11 +1985,14 @@ var PinnedClientAdapter = class {
1922
1985
  tx(callback) {
1923
1986
  return this.#enqueue(async () => {
1924
1987
  await this.pinnedClient.query("BEGIN");
1988
+ const txClient = new TransactionClient(this.pinnedClient);
1925
1989
  try {
1926
- const result = await callback(new TransactionClient(this.pinnedClient));
1990
+ const result = await callback(txClient);
1991
+ await txClient.drain();
1927
1992
  await this.pinnedClient.query("COMMIT");
1928
1993
  return result;
1929
1994
  } catch (error) {
1995
+ await txClient.drain().catch(() => void 0);
1930
1996
  try {
1931
1997
  await this.pinnedClient.query("ROLLBACK");
1932
1998
  } catch (rollbackError) {
@@ -6416,7 +6482,13 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6416
6482
  ifNotExists: [
6417
6483
  "agentVersion",
6418
6484
  "organizationId",
6419
- "projectId"
6485
+ "projectId",
6486
+ "provenance",
6487
+ "runnerAttestation",
6488
+ "experimentSetId",
6489
+ "comparisonId",
6490
+ "variantId",
6491
+ "trialIndex"
6420
6492
  ]
6421
6493
  });
6422
6494
  await this.#db.alterTable({
@@ -6522,6 +6594,16 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6522
6594
  table: TABLE_EXPERIMENTS,
6523
6595
  columns: ["datasetId"]
6524
6596
  },
6597
+ {
6598
+ name: "idx_experiments_grouping",
6599
+ table: TABLE_EXPERIMENTS,
6600
+ columns: [
6601
+ "experimentSetId",
6602
+ "comparisonId",
6603
+ "variantId",
6604
+ "trialIndex"
6605
+ ]
6606
+ },
6525
6607
  {
6526
6608
  name: "idx_experiment_results_experimentid",
6527
6609
  table: TABLE_EXPERIMENT_RESULTS,
@@ -6567,6 +6649,12 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6567
6649
  name: row.name ?? void 0,
6568
6650
  description: row.description ?? void 0,
6569
6651
  metadata: row.metadata ? safelyParseJSON(row.metadata) : void 0,
6652
+ provenance: row.provenance ? safelyParseJSON(row.provenance) : null,
6653
+ runnerAttestation: row.runnerAttestation ? safelyParseJSON(row.runnerAttestation) : null,
6654
+ experimentSetId: row.experimentSetId ?? null,
6655
+ comparisonId: row.comparisonId ?? null,
6656
+ variantId: row.variantId ?? null,
6657
+ trialIndex: row.trialIndex != null ? row.trialIndex : null,
6570
6658
  datasetId: row.datasetId ?? null,
6571
6659
  datasetVersion: row.datasetVersion != null ? row.datasetVersion : null,
6572
6660
  agentVersion: row.agentVersion ?? null,
@@ -6620,6 +6708,12 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6620
6708
  name: input.name ?? null,
6621
6709
  description: input.description ?? null,
6622
6710
  metadata: input.metadata ?? null,
6711
+ provenance: input.provenance ?? null,
6712
+ runnerAttestation: input.runnerAttestation ?? null,
6713
+ experimentSetId: input.experimentSetId ?? null,
6714
+ comparisonId: input.comparisonId ?? null,
6715
+ variantId: input.variantId ?? null,
6716
+ trialIndex: input.trialIndex ?? null,
6623
6717
  datasetId: input.datasetId ?? null,
6624
6718
  datasetVersion: input.datasetVersion ?? null,
6625
6719
  agentVersion: input.agentVersion ?? null,
@@ -6643,6 +6737,12 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6643
6737
  name: input.name,
6644
6738
  description: input.description,
6645
6739
  metadata: input.metadata,
6740
+ provenance: input.provenance ?? null,
6741
+ runnerAttestation: input.runnerAttestation ?? null,
6742
+ experimentSetId: input.experimentSetId ?? null,
6743
+ comparisonId: input.comparisonId ?? null,
6744
+ variantId: input.variantId ?? null,
6745
+ trialIndex: input.trialIndex ?? null,
6646
6746
  datasetId: input.datasetId ?? null,
6647
6747
  datasetVersion: input.datasetVersion ?? null,
6648
6748
  agentVersion: input.agentVersion ?? null,
@@ -6784,6 +6884,22 @@ var ExperimentsPG = class ExperimentsPG extends ExperimentsStorage {
6784
6884
  conditions.push(`"status" = $${paramIndex++}`);
6785
6885
  queryParams.push(args.status);
6786
6886
  }
6887
+ if (args.experimentSetId !== void 0) {
6888
+ conditions.push(`"experimentSetId" = $${paramIndex++}`);
6889
+ queryParams.push(args.experimentSetId);
6890
+ }
6891
+ if (args.comparisonId !== void 0) {
6892
+ conditions.push(`"comparisonId" = $${paramIndex++}`);
6893
+ queryParams.push(args.comparisonId);
6894
+ }
6895
+ if (args.variantId !== void 0) {
6896
+ conditions.push(`"variantId" = $${paramIndex++}`);
6897
+ queryParams.push(args.variantId);
6898
+ }
6899
+ if (args.trialIndex !== void 0) {
6900
+ conditions.push(`"trialIndex" = $${paramIndex++}`);
6901
+ queryParams.push(args.trialIndex);
6902
+ }
6787
6903
  if (args.filters) {
6788
6904
  const { organizationId, projectId } = args.filters;
6789
6905
  if (organizationId !== void 0) {
@@ -8565,6 +8681,13 @@ function getTableName$3({ indexName, schemaName }) {
8565
8681
  function inPlaceholders(count, startIndex = 1) {
8566
8682
  return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
8567
8683
  }
8684
+ /**
8685
+ * Bind dates as UTC strings because node-postgres serializes Date parameters
8686
+ * for TIMESTAMP columns using the process's local timezone.
8687
+ */
8688
+ function toUtcISOString(date) {
8689
+ return date.toISOString();
8690
+ }
8568
8691
  function dedupeMessagesForSave(messages) {
8569
8692
  const deduped = /* @__PURE__ */ new Map();
8570
8693
  for (const message of messages) {
@@ -8979,6 +9102,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
8979
9102
  hasMore: perPageInput === false ? false : offset + perPage < total
8980
9103
  };
8981
9104
  } catch (error) {
9105
+ if (error instanceof MastraError && error.category === ErrorCategory.USER) throw error;
8982
9106
  const mastraError = new MastraError({
8983
9107
  id: createStorageErrorId("PG", "LIST_THREADS", "FAILED"),
8984
9108
  domain: ErrorDomain.STORAGE,
@@ -8991,13 +9115,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
8991
9115
  }, error);
8992
9116
  this.logger?.error?.(mastraError.toString());
8993
9117
  this.logger?.trackException(mastraError);
8994
- return {
8995
- threads: [],
8996
- total: 0,
8997
- page,
8998
- perPage: perPageForResponse,
8999
- hasMore: false
9000
- };
9118
+ throw mastraError;
9001
9119
  }
9002
9120
  }
9003
9121
  async saveThread({ thread }) {
@@ -9006,6 +9124,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9006
9124
  indexName: TABLE_THREADS,
9007
9125
  schemaName: getSchemaName$3(this.#schema)
9008
9126
  });
9127
+ const createdAt = toUtcISOString(thread.createdAt);
9128
+ const updatedAt = toUtcISOString(thread.updatedAt);
9009
9129
  await this.#db.client.none(`INSERT INTO ${tableName} (
9010
9130
  id,
9011
9131
  "resourceId",
@@ -9028,10 +9148,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9028
9148
  thread.resourceId,
9029
9149
  thread.title,
9030
9150
  thread.metadata ? JSON.stringify(thread.metadata) : null,
9031
- thread.createdAt,
9032
- thread.createdAt,
9033
- thread.updatedAt,
9034
- thread.updatedAt
9151
+ createdAt,
9152
+ createdAt,
9153
+ updatedAt,
9154
+ updatedAt
9035
9155
  ]);
9036
9156
  return thread;
9037
9157
  } catch (error) {
@@ -9064,7 +9184,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9064
9184
  ...metadata
9065
9185
  };
9066
9186
  try {
9067
- const now = /* @__PURE__ */ new Date();
9187
+ const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
9068
9188
  const thread = await this.#db.client.one(`UPDATE ${threadTableName}
9069
9189
  SET
9070
9190
  title = $1,
@@ -9076,8 +9196,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9076
9196
  `, [
9077
9197
  title,
9078
9198
  mergedMetadata,
9079
- now,
9080
- now,
9199
+ nowStr,
9200
+ nowStr,
9081
9201
  id
9082
9202
  ]);
9083
9203
  return {
@@ -9266,9 +9386,41 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9266
9386
  }, error);
9267
9387
  this.logger?.error?.(mastraError.toString());
9268
9388
  this.logger?.trackException(mastraError);
9269
- return { messages: [] };
9389
+ throw mastraError;
9270
9390
  }
9271
9391
  }
9392
+ /**
9393
+ * Reads one page of messages together with the total row count.
9394
+ *
9395
+ * `COUNT(*) OVER ()` reports the count over the whole WHERE result on the same
9396
+ * statement as the page, so the page costs one database round-trip instead of
9397
+ * two. The page and the count also come from one snapshot, so the count always
9398
+ * describes the returned rows. A separate `COUNT(*)` runs only when the page is
9399
+ * empty and the caller asked for a page after the last row, because a window
9400
+ * function has no row to carry the count on.
9401
+ */
9402
+ async #fetchMessagePage({ selectStatement, tableName, whereClause, orderByStatement, queryParams, perPageInput, perPage, offset }) {
9403
+ const limitClause = perPageInput === false ? "" : ` LIMIT $${queryParams.length + 1} OFFSET $${queryParams.length + 2}`;
9404
+ const dataParams = perPageInput === false ? queryParams : [
9405
+ ...queryParams,
9406
+ perPage,
9407
+ offset
9408
+ ];
9409
+ const rows = await this.#db.client.manyOrNone(`${selectStatement}, COUNT(*) OVER () AS "__total" FROM ${tableName} ${whereClause} ${orderByStatement}${limitClause}`, dataParams) || [];
9410
+ if (rows.length > 0) return {
9411
+ total: Number(rows[0].__total),
9412
+ messages: rows
9413
+ };
9414
+ if (offset === 0) return {
9415
+ total: 0,
9416
+ messages: []
9417
+ };
9418
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) FROM ${tableName} ${whereClause}`, queryParams);
9419
+ return {
9420
+ total: parseInt(countResult.count, 10),
9421
+ messages: []
9422
+ };
9423
+ }
9272
9424
  async listMessages(args) {
9273
9425
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
9274
9426
  const threadIds = (Array.isArray(threadId) ? threadId : [threadId]).filter((id) => typeof id === "string");
@@ -9343,23 +9495,27 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9343
9495
  hasMore: false
9344
9496
  };
9345
9497
  }
9498
+ let includeFailure;
9499
+ const includePromise = include && include.length > 0 ? this._getIncludedMessages({ include }).catch((error) => {
9500
+ includeFailure = error;
9501
+ return null;
9502
+ }) : null;
9346
9503
  let total;
9347
9504
  let messages;
9348
9505
  if (metadataFilter) {
9349
9506
  const filteredRows = (await this.#db.client.manyOrNone(`${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement}`, queryParams) || []).filter((row) => storageMessageMatchesMetadataFilter(row.content, metadataFilter));
9350
9507
  total = filteredRows.length;
9351
9508
  messages = perPageInput === false ? filteredRows : filteredRows.slice(offset, offset + perPage);
9352
- } else {
9353
- const countResult = await this.#db.client.one(`SELECT COUNT(*) FROM ${tableName} ${whereClause}`, queryParams);
9354
- total = parseInt(countResult.count, 10);
9355
- const limitValue = perPageInput === false ? total : perPage;
9356
- const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9357
- messages = [...await this.#db.client.manyOrNone(dataQuery, [
9358
- ...queryParams,
9359
- limitValue,
9360
- offset
9361
- ]) || []];
9362
- }
9509
+ } else ({total, messages} = await this.#fetchMessagePage({
9510
+ selectStatement,
9511
+ tableName,
9512
+ whereClause,
9513
+ orderByStatement,
9514
+ queryParams,
9515
+ perPageInput,
9516
+ perPage,
9517
+ offset
9518
+ }));
9363
9519
  const primaryPageCount = messages.length;
9364
9520
  if (total === 0 && messages.length === 0 && (!include || include.length === 0)) return {
9365
9521
  messages: [],
@@ -9370,7 +9526,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9370
9526
  };
9371
9527
  const messageIds = new Set(messages.map((m) => m.id));
9372
9528
  if (include && include.length > 0) {
9373
- const includeMessages = await this._getIncludedMessages({ include });
9529
+ const includeMessages = await includePromise;
9530
+ if (includeFailure) throw includeFailure;
9374
9531
  if (includeMessages) {
9375
9532
  for (const includeMsg of includeMessages) if (!messageIds.has(includeMsg.id)) {
9376
9533
  messages.push(includeMsg);
@@ -9392,6 +9549,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9392
9549
  hasMore
9393
9550
  };
9394
9551
  } catch (error) {
9552
+ if (error instanceof MastraError && error.category === ErrorCategory.USER) throw error;
9395
9553
  const mastraError = new MastraError({
9396
9554
  id: createStorageErrorId("PG", "LIST_MESSAGES", "FAILED"),
9397
9555
  domain: ErrorDomain.STORAGE,
@@ -9403,13 +9561,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9403
9561
  }, error);
9404
9562
  this.logger?.error?.(mastraError.toString());
9405
9563
  this.logger?.trackException(mastraError);
9406
- return {
9407
- messages: [],
9408
- total: 0,
9409
- page,
9410
- perPage: perPageForResponse,
9411
- hasMore: false
9412
- };
9564
+ throw mastraError;
9413
9565
  }
9414
9566
  }
9415
9567
  async listMessagesByResourceId(args) {
@@ -9483,23 +9635,27 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9483
9635
  hasMore: false
9484
9636
  };
9485
9637
  }
9638
+ let includeFailure;
9639
+ const includePromise = include && include.length > 0 ? this._getIncludedMessages({ include }).catch((error) => {
9640
+ includeFailure = error;
9641
+ return null;
9642
+ }) : null;
9486
9643
  let total;
9487
9644
  let messages;
9488
9645
  if (metadataFilter) {
9489
9646
  const filteredRows = (await this.#db.client.manyOrNone(`${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement}`, queryParams) || []).filter((row) => storageMessageMatchesMetadataFilter(row.content, metadataFilter));
9490
9647
  total = filteredRows.length;
9491
9648
  messages = perPageInput === false ? filteredRows : filteredRows.slice(offset, offset + perPage);
9492
- } else {
9493
- const countResult = await this.#db.client.one(`SELECT COUNT(*) FROM ${tableName} ${whereClause}`, queryParams);
9494
- total = parseInt(countResult.count, 10);
9495
- const limitValue = perPageInput === false ? total : perPage;
9496
- const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9497
- messages = [...await this.#db.client.manyOrNone(dataQuery, [
9498
- ...queryParams,
9499
- limitValue,
9500
- offset
9501
- ]) || []];
9502
- }
9649
+ } else ({total, messages} = await this.#fetchMessagePage({
9650
+ selectStatement,
9651
+ tableName,
9652
+ whereClause,
9653
+ orderByStatement,
9654
+ queryParams,
9655
+ perPageInput,
9656
+ perPage,
9657
+ offset
9658
+ }));
9503
9659
  if (total === 0 && messages.length === 0 && (!include || include.length === 0)) return {
9504
9660
  messages: [],
9505
9661
  total: 0,
@@ -9509,7 +9665,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9509
9665
  };
9510
9666
  const messageIds = new Set(messages.map((m) => m.id));
9511
9667
  if (include && include.length > 0) {
9512
- const includeMessages = await this._getIncludedMessages({ include });
9668
+ const includeMessages = await includePromise;
9669
+ if (includeFailure) throw includeFailure;
9513
9670
  if (includeMessages) {
9514
9671
  for (const includeMsg of includeMessages) if (!messageIds.has(includeMsg.id)) {
9515
9672
  messages.push(includeMsg);
@@ -9529,6 +9686,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9529
9686
  hasMore
9530
9687
  };
9531
9688
  } catch (error) {
9689
+ if (error instanceof MastraError && error.category === ErrorCategory.USER) throw error;
9532
9690
  const mastraError = new MastraError({
9533
9691
  id: createStorageErrorId("PG", "LIST_MESSAGES_BY_RESOURCE_ID", "FAILED"),
9534
9692
  domain: ErrorDomain.STORAGE,
@@ -9537,13 +9695,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9537
9695
  }, error);
9538
9696
  this.logger?.error?.(mastraError.toString());
9539
9697
  this.logger?.trackException(mastraError);
9540
- return {
9541
- messages: [],
9542
- total: 0,
9543
- page,
9544
- perPage: perPageForResponse,
9545
- hasMore: false
9546
- };
9698
+ throw mastraError;
9547
9699
  }
9548
9700
  }
9549
9701
  async saveMessages({ messages }) {
@@ -9579,7 +9731,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9579
9731
  const batch = messagesToSave.slice(offset, offset + MAX_MESSAGES_PER_INSERT);
9580
9732
  const values = [];
9581
9733
  const valuePlaceholders = batch.map((message, messageIndex) => {
9582
- const createdAt = message.createdAt || /* @__PURE__ */ new Date();
9734
+ const createdAt = toUtcISOString(message.createdAt || /* @__PURE__ */ new Date());
9583
9735
  values.push(message.id, message.threadId, typeof message.content === "string" ? message.content : JSON.stringify(message.content), createdAt, createdAt, message.role, message.type || "v2", message.resourceId);
9584
9736
  const paramOffset = messageIndex * MESSAGE_INSERT_BIND_PARAMETERS;
9585
9737
  return `(${Array.from({ length: MESSAGE_INSERT_BIND_PARAMETERS }, (_, paramIndex) => `$${paramOffset + paramIndex + 1}`).join(", ")})`;
@@ -9597,7 +9749,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9597
9749
  indexName: TABLE_THREADS,
9598
9750
  schemaName: getSchemaName$3(this.#schema)
9599
9751
  });
9600
- const now = /* @__PURE__ */ new Date();
9752
+ const now = toUtcISOString(/* @__PURE__ */ new Date());
9601
9753
  for (const threadIdToUpdate of threadIds) await t.none(`UPDATE ${threadTableName}
9602
9754
  SET
9603
9755
  "updatedAt" = $1,
@@ -9749,11 +9901,15 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9749
9901
  };
9750
9902
  }
9751
9903
  async saveResource({ resource }) {
9904
+ const createdAt = toUtcISOString(resource.createdAt);
9905
+ const updatedAt = toUtcISOString(resource.updatedAt);
9752
9906
  await this.#db.insert({
9753
9907
  tableName: TABLE_RESOURCES,
9754
9908
  record: {
9755
9909
  ...resource,
9756
- metadata: JSON.stringify(resource.metadata)
9910
+ metadata: JSON.stringify(resource.metadata),
9911
+ createdAt,
9912
+ updatedAt
9757
9913
  }
9758
9914
  });
9759
9915
  return resource;
@@ -9857,6 +10013,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9857
10013
  }
9858
10014
  const sourceMessages = await t.manyOrNone(messageQuery, messageParams);
9859
10015
  const now = /* @__PURE__ */ new Date();
10016
+ const nowStr = toUtcISOString(now);
9860
10017
  const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
9861
10018
  const cloneMetadata = {
9862
10019
  sourceThreadId,
@@ -9888,10 +10045,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9888
10045
  newThread.resourceId,
9889
10046
  newThread.title,
9890
10047
  newThread.metadata ? JSON.stringify(newThread.metadata) : null,
9891
- now,
9892
- now,
9893
- now,
9894
- now
10048
+ nowStr,
10049
+ nowStr,
10050
+ nowStr,
10051
+ nowStr
9895
10052
  ]);
9896
10053
  const clonedMessages = [];
9897
10054
  const messageIdMap = {};
@@ -9904,13 +10061,14 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
9904
10061
  try {
9905
10062
  parsedContent = JSON.parse(normalizedMsg.content);
9906
10063
  } catch {}
10064
+ const createdAt = toUtcISOString(new Date(normalizedMsg.createdAt));
9907
10065
  await t.none(`INSERT INTO ${messageTableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
9908
10066
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
9909
10067
  newMessageId,
9910
10068
  newThreadId,
9911
10069
  typeof normalizedMsg.content === "string" ? normalizedMsg.content : JSON.stringify(normalizedMsg.content),
9912
- normalizedMsg.createdAt,
9913
- normalizedMsg.createdAt,
10070
+ createdAt,
10071
+ createdAt,
9914
10072
  normalizedMsg.role,
9915
10073
  normalizedMsg.type || "v2",
9916
10074
  targetResourceId