@mastra/pg 1.17.0 → 1.17.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 1.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Improved Factory work-item concurrency by replacing distributed advisory locks with atomic claims, idempotent replay, and serializable relationship transactions. ([#20135](https://github.com/mastra-ai/mastra/pull/20135))
8
+
9
+ - Hardened distributed lock cleanup so clients are destroyed when transaction rollback fails. ([#20135](https://github.com/mastra-ai/mastra/pull/20135))
10
+
11
+ - Updated dependencies [[`c8d8a01`](https://github.com/mastra-ai/mastra/commit/c8d8a010ee2efe2b7bf4d07707382c34c87b14e4), [`df6a9ce`](https://github.com/mastra-ai/mastra/commit/df6a9ce87214f7aadb2edfe62f67605fe998a0a4), [`73839cb`](https://github.com/mastra-ai/mastra/commit/73839cb58322679c170627d1015669ede5f619aa), [`371cf60`](https://github.com/mastra-ai/mastra/commit/371cf6075cef88ac6919a08d59a82e485397364a), [`8e4dc79`](https://github.com/mastra-ai/mastra/commit/8e4dc793dcf035ea506f9ce79f56d2d501a4be14), [`2db93cc`](https://github.com/mastra-ai/mastra/commit/2db93ccd0b872e4de7853a93383efe0647901df8), [`094ab61`](https://github.com/mastra-ai/mastra/commit/094ab6129a1a3ecf6eeb86decac17d5faea4e02a), [`fe80944`](https://github.com/mastra-ai/mastra/commit/fe80944f3ef6681fea6eae8200fce387b7bb3c2f), [`263d2ca`](https://github.com/mastra-ai/mastra/commit/263d2cac80ba3b03b9c0f008db6f1f1b9eb0278c), [`75f843d`](https://github.com/mastra-ai/mastra/commit/75f843d09f758223e6eeb321321bdcc5c7e779d0), [`e51e166`](https://github.com/mastra-ai/mastra/commit/e51e166c52e220abc9b64554ce37359dca8544b1)]:
12
+ - @mastra/core@1.53.0
13
+
14
+ ## 1.17.1-alpha.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Improved Factory work-item concurrency by replacing distributed advisory locks with atomic claims, idempotent replay, and serializable relationship transactions. ([#20135](https://github.com/mastra-ai/mastra/pull/20135))
19
+
20
+ - Hardened distributed lock cleanup so clients are destroyed when transaction rollback fails. ([#20135](https://github.com/mastra-ai/mastra/pull/20135))
21
+
22
+ - Updated dependencies [[`73839cb`](https://github.com/mastra-ai/mastra/commit/73839cb58322679c170627d1015669ede5f619aa), [`8e4dc79`](https://github.com/mastra-ai/mastra/commit/8e4dc793dcf035ea506f9ce79f56d2d501a4be14), [`2db93cc`](https://github.com/mastra-ai/mastra/commit/2db93ccd0b872e4de7853a93383efe0647901df8), [`094ab61`](https://github.com/mastra-ai/mastra/commit/094ab6129a1a3ecf6eeb86decac17d5faea4e02a), [`fe80944`](https://github.com/mastra-ai/mastra/commit/fe80944f3ef6681fea6eae8200fce387b7bb3c2f), [`e51e166`](https://github.com/mastra-ai/mastra/commit/e51e166c52e220abc9b64554ce37359dca8544b1)]:
23
+ - @mastra/core@1.53.0-alpha.4
24
+
3
25
  ## 1.17.0
4
26
 
5
27
  ### Minor Changes
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.17.0"
6
+ version: "1.17.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.17.0",
2
+ "version": "1.17.1",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -47,7 +47,7 @@ export const agent = new Agent({
47
47
 
48
48
  **options.observationalMemory** (`boolean | ObservationalMemoryOptions`): Enable Observational Memory for long-context agentic memory. Set to true for defaults, or pass a config object to customize token budgets, models, and scope. See Observational Memory reference for configuration details.
49
49
 
50
- **options.generateTitle** (`boolean | { model: DynamicArgument<MastraLanguageModel>; instructions?: DynamicArgument<string> }`): Controls automatic thread title generation from the user's first message. Can be a boolean or an object with custom model and instructions.
50
+ **options.generateTitle** (`boolean | { model: DynamicArgument<MastraLanguageModel>; instructions?: DynamicArgument<string> }`): Controls automatic thread title generation from the conversation transcript. Can be a boolean or an object with custom model and instructions.
51
51
 
52
52
  ## Returns
53
53
 
package/dist/index.cjs CHANGED
@@ -9598,6 +9598,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
9598
9598
  }
9599
9599
  const perPage = storage.normalizePerPage(perPageInput, 40);
9600
9600
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
9601
+ const metadataFilter = storage.validateStorageMetadataFilter(filter?.metadata);
9601
9602
  try {
9602
9603
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
9603
9604
  const orderByStatement = `ORDER BY COALESCE("${field}Z", "${field}") ${direction}`;
@@ -9639,13 +9640,27 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
9639
9640
  hasMore: false
9640
9641
  };
9641
9642
  }
9642
- const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
9643
- const countResult = await this.#db.client.one(countQuery, queryParams);
9644
- const total = parseInt(countResult.count, 10);
9645
- const limitValue = perPageInput === false ? total : perPage;
9646
- const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9647
- const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
9648
- const messages = [...rows || []];
9643
+ let total;
9644
+ let messages;
9645
+ if (metadataFilter) {
9646
+ const rows = await this.#db.client.manyOrNone(
9647
+ `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement}`,
9648
+ queryParams
9649
+ );
9650
+ const filteredRows = (rows || []).filter(
9651
+ (row) => storage.storageMessageMatchesMetadataFilter(row.content, metadataFilter)
9652
+ );
9653
+ total = filteredRows.length;
9654
+ messages = perPageInput === false ? filteredRows : filteredRows.slice(offset, offset + perPage);
9655
+ } else {
9656
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) FROM ${tableName} ${whereClause}`, queryParams);
9657
+ total = parseInt(countResult.count, 10);
9658
+ const limitValue = perPageInput === false ? total : perPage;
9659
+ const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9660
+ const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
9661
+ messages = [...rows || []];
9662
+ }
9663
+ const primaryPageCount = messages.length;
9649
9664
  if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
9650
9665
  return {
9651
9666
  messages: [],
@@ -9675,7 +9690,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
9675
9690
  finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
9676
9691
  );
9677
9692
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
9678
- const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
9693
+ const hasMore = metadataFilter ? perPageInput !== false && offset + primaryPageCount < total : perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
9679
9694
  return {
9680
9695
  messages: finalMessages,
9681
9696
  total,
@@ -9737,6 +9752,7 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
9737
9752
  }
9738
9753
  const perPage = storage.normalizePerPage(perPageInput, 40);
9739
9754
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
9755
+ const metadataFilter = storage.validateStorageMetadataFilter(filter?.metadata);
9740
9756
  try {
9741
9757
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
9742
9758
  const orderByStatement = `ORDER BY COALESCE("${field}Z", "${field}") ${direction}`;
@@ -9782,13 +9798,26 @@ var MemoryPG = class _MemoryPG extends storage.MemoryStorage {
9782
9798
  hasMore: false
9783
9799
  };
9784
9800
  }
9785
- const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
9786
- const countResult = await this.#db.client.one(countQuery, queryParams);
9787
- const total = parseInt(countResult.count, 10);
9788
- const limitValue = perPageInput === false ? total : perPage;
9789
- const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9790
- const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
9791
- const messages = [...rows || []];
9801
+ let total;
9802
+ let messages;
9803
+ if (metadataFilter) {
9804
+ const rows = await this.#db.client.manyOrNone(
9805
+ `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement}`,
9806
+ queryParams
9807
+ );
9808
+ const filteredRows = (rows || []).filter(
9809
+ (row) => storage.storageMessageMatchesMetadataFilter(row.content, metadataFilter)
9810
+ );
9811
+ total = filteredRows.length;
9812
+ messages = perPageInput === false ? filteredRows : filteredRows.slice(offset, offset + perPage);
9813
+ } else {
9814
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) FROM ${tableName} ${whereClause}`, queryParams);
9815
+ total = parseInt(countResult.count, 10);
9816
+ const limitValue = perPageInput === false ? total : perPage;
9817
+ const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
9818
+ const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
9819
+ messages = [...rows || []];
9820
+ }
9792
9821
  if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
9793
9822
  return {
9794
9823
  messages: [],
@@ -20250,10 +20279,6 @@ function serializeDefault(value) {
20250
20279
  if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
20251
20280
  return String(value);
20252
20281
  }
20253
- function hashAdvisoryLockKey(key) {
20254
- const digest = crypto$1.createHash("sha256").update(key).digest();
20255
- return [digest.readInt32BE(0), digest.readInt32BE(4)];
20256
- }
20257
20282
  var PgFactoryStorageOps = class {
20258
20283
  #queryable;
20259
20284
  #transactionClient;
@@ -20548,21 +20573,36 @@ var PgFactoryStorage = class extends storage.FactoryStorage {
20548
20573
  async initStorage() {
20549
20574
  await this.#pool.query("SELECT 1");
20550
20575
  }
20551
- async withTransaction(fn) {
20552
- const client = await this.#pool.connect();
20553
- try {
20554
- await client.query("BEGIN");
20576
+ async withTransaction(fn, options) {
20577
+ const maxAttempts = options?.isolationLevel === "serializable" ? 3 : 1;
20578
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
20579
+ const client = await this.#pool.connect();
20580
+ let transactionOpen = false;
20581
+ let releaseError;
20555
20582
  try {
20583
+ await client.query(options?.isolationLevel === "serializable" ? "BEGIN ISOLATION LEVEL SERIALIZABLE" : "BEGIN");
20584
+ transactionOpen = true;
20556
20585
  const result = await fn(new PgFactoryStorageOps(client, this.#schemas, client));
20557
20586
  await client.query("COMMIT");
20587
+ transactionOpen = false;
20558
20588
  return result;
20559
20589
  } catch (error) {
20560
- await client.query("ROLLBACK");
20561
- throw error;
20590
+ if (transactionOpen) {
20591
+ try {
20592
+ await client.query("ROLLBACK");
20593
+ transactionOpen = false;
20594
+ } catch (rollbackError) {
20595
+ releaseError = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));
20596
+ throw new AggregateError([error, rollbackError], "Factory transaction and rollback both failed");
20597
+ }
20598
+ }
20599
+ const serializationFailure = typeof error === "object" && error !== null && error.code === "40001";
20600
+ if (!serializationFailure || attempt === maxAttempts) throw error;
20601
+ } finally {
20602
+ client.release(releaseError);
20562
20603
  }
20563
- } finally {
20564
- client.release();
20565
20604
  }
20605
+ throw new Error("PgFactoryStorage: serializable transaction retry limit exceeded");
20566
20606
  }
20567
20607
  async ensureCollections(schemas) {
20568
20608
  for (const schema of schemas) {
@@ -20580,30 +20620,6 @@ var PgFactoryStorage = class extends storage.FactoryStorage {
20580
20620
  authDatabase() {
20581
20621
  return { dialect: "postgres", pool: this.#pool };
20582
20622
  }
20583
- /**
20584
- * Run `fn` while holding a Postgres transaction-scoped advisory lock for
20585
- * `key`, serializing callers across replicas. The lock releases when the
20586
- * transaction ends (commit, rollback, or connection loss), so a crashed
20587
- * replica can never hold it forever.
20588
- */
20589
- async withDistributedLock(key, fn) {
20590
- const [k1, k2] = hashAdvisoryLockKey(key);
20591
- const client = await this.#pool.connect();
20592
- try {
20593
- await client.query("BEGIN");
20594
- await client.query("SELECT pg_advisory_xact_lock($1, $2)", [k1, k2]);
20595
- try {
20596
- const result = await fn();
20597
- await client.query("COMMIT");
20598
- return result;
20599
- } catch (error) {
20600
- await client.query("ROLLBACK");
20601
- throw error;
20602
- }
20603
- } finally {
20604
- client.release();
20605
- }
20606
- }
20607
20623
  #columnDdl(name, spec) {
20608
20624
  assertIdentifier("column", name);
20609
20625
  let ddl = `"${name}" ${COLUMN_DDL[spec.type]}`;
@@ -21067,6 +21083,5 @@ exports.ToolProviderConnectionsPG = ToolProviderConnectionsPG;
21067
21083
  exports.WorkflowsPG = WorkflowsPG;
21068
21084
  exports.WorkspacesPG = WorkspacesPG;
21069
21085
  exports.exportSchemas = exportSchemas;
21070
- exports.hashAdvisoryLockKey = hashAdvisoryLockKey;
21071
21086
  //# sourceMappingURL=index.cjs.map
21072
21087
  //# sourceMappingURL=index.cjs.map