@mastra/pg 1.27.1 → 1.27.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10349,6 +10349,12 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
10349
10349
  * versions that don't export TABLE_OBSERVATIONAL_MEMORY.
10350
10350
  */
10351
10351
  const OM_TABLE = "mastra_observational_memory";
10352
+ /**
10353
+ * Newest generation first. Databases written before generation creation was
10354
+ * serialized can hold several rows with the same generation; the earliest-created
10355
+ * one wins so the active record stays stable across reads.
10356
+ */
10357
+ const OM_GENERATION_ORDER = `"generationCount" DESC, "createdAt" ASC, id ASC`;
10352
10358
  const POSTGRES_MAX_BIND_PARAMETERS = 65535;
10353
10359
  const MESSAGE_INSERT_BIND_PARAMETERS = 8;
10354
10360
  const MAX_MESSAGES_PER_INSERT = Math.floor(POSTGRES_MAX_BIND_PARAMETERS / MESSAGE_INSERT_BIND_PARAMETERS);
@@ -11950,6 +11956,23 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11950
11956
  getOMKey(threadId, resourceId) {
11951
11957
  return threadId ? `thread:${threadId}` : `resource:${resourceId}`;
11952
11958
  }
11959
+ /**
11960
+ * Runs `fn` in a transaction that holds an advisory lock for one OM lookup key.
11961
+ *
11962
+ * The table has no unique constraint on ("lookupKey", "generationCount"), and
11963
+ * adding one would fail on databases that already contain duplicates. The lock
11964
+ * serializes generation creation across processes instead. It is
11965
+ * transaction-scoped so it also works behind transaction-pooling proxies.
11966
+ */
11967
+ async #withOMLookupKeyLock(tableName, lookupKey, fn) {
11968
+ return this.#db.client.tx(async (t) => {
11969
+ await t.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${tableName}:${lookupKey}`]);
11970
+ return fn(t);
11971
+ });
11972
+ }
11973
+ async #getLatestOMRow(client, tableName, lookupKey) {
11974
+ return client.oneOrNone(`SELECT * FROM ${tableName} WHERE "lookupKey" = $1 ORDER BY ${OM_GENERATION_ORDER} LIMIT 1`, [lookupKey]);
11975
+ }
11953
11976
  parseOMRow(row) {
11954
11977
  return {
11955
11978
  id: row.id,
@@ -11992,7 +12015,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11992
12015
  indexName: OM_TABLE,
11993
12016
  schemaName: getSchemaName$3(this.#schema)
11994
12017
  });
11995
- const result = await this.#db.readClient.oneOrNone(`SELECT * FROM ${tableName} WHERE "lookupKey" = $1 ORDER BY "generationCount" DESC LIMIT 1`, [lookupKey]);
12018
+ const result = await this.#getLatestOMRow(this.#db.readClient, tableName, lookupKey);
11996
12019
  if (!result) return null;
11997
12020
  return this.parseOMRow(result);
11998
12021
  } catch (error) {
@@ -12028,7 +12051,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12028
12051
  paramIndex++;
12029
12052
  }
12030
12053
  params.push(limit);
12031
- let sql = `SELECT * FROM ${tableName} WHERE ${conditions.join(" AND ")} ORDER BY "generationCount" DESC LIMIT $${paramIndex}`;
12054
+ let sql = `SELECT * FROM ${tableName} WHERE ${conditions.join(" AND ")} ORDER BY ${OM_GENERATION_ORDER} LIMIT $${paramIndex}`;
12032
12055
  paramIndex++;
12033
12056
  if (options?.offset != null) {
12034
12057
  params.push(options.offset);
@@ -12083,7 +12106,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12083
12106
  schemaName: getSchemaName$3(this.#schema)
12084
12107
  });
12085
12108
  const nowStr = now.toISOString();
12086
- await this.#db.client.none(`INSERT INTO ${tableName} (
12109
+ return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
12110
+ const existing = await this.#getLatestOMRow(t, tableName, lookupKey);
12111
+ if (existing) return this.parseOMRow(existing);
12112
+ await t.none(`INSERT INTO ${tableName} (
12087
12113
  id, "lookupKey", scope, "resourceId", "threadId",
12088
12114
  "activeObservations", "activeObservationsPendingUpdate",
12089
12115
  "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
@@ -12091,36 +12117,37 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12091
12117
  "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
12092
12118
  "observedTimezone", "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
12093
12119
  ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)`, [
12094
- id,
12095
- lookupKey,
12096
- input.scope,
12097
- input.resourceId,
12098
- input.threadId || null,
12099
- "",
12100
- null,
12101
- "initial",
12102
- toPgJson(input.config),
12103
- 0,
12104
- null,
12105
- null,
12106
- null,
12107
- null,
12108
- 0,
12109
- 0,
12110
- 0,
12111
- false,
12112
- false,
12113
- false,
12114
- false,
12115
- 0,
12116
- null,
12117
- input.observedTimezone || null,
12118
- nowStr,
12119
- nowStr,
12120
- nowStr,
12121
- nowStr
12122
- ]);
12123
- return record;
12120
+ id,
12121
+ lookupKey,
12122
+ input.scope,
12123
+ input.resourceId,
12124
+ input.threadId || null,
12125
+ "",
12126
+ null,
12127
+ "initial",
12128
+ toPgJson(input.config),
12129
+ 0,
12130
+ null,
12131
+ null,
12132
+ null,
12133
+ null,
12134
+ 0,
12135
+ 0,
12136
+ 0,
12137
+ false,
12138
+ false,
12139
+ false,
12140
+ false,
12141
+ 0,
12142
+ null,
12143
+ input.observedTimezone || null,
12144
+ nowStr,
12145
+ nowStr,
12146
+ nowStr,
12147
+ nowStr
12148
+ ]);
12149
+ return record;
12150
+ });
12124
12151
  } catch (error) {
12125
12152
  throw new MastraError({
12126
12153
  id: createStorageErrorId("PG", "INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED"),
@@ -12252,78 +12279,16 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12252
12279
  }
12253
12280
  async createReflectionGeneration(input) {
12254
12281
  try {
12255
- const id = crypto.randomUUID();
12256
- const now = /* @__PURE__ */ new Date();
12257
12282
  const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
12258
- const record = {
12259
- id,
12260
- scope: input.currentRecord.scope,
12261
- threadId: input.currentRecord.threadId,
12262
- resourceId: input.currentRecord.resourceId,
12263
- createdAt: now,
12264
- updatedAt: now,
12265
- lastObservedAt: input.currentRecord.lastObservedAt,
12266
- originType: "reflection",
12267
- generationCount: input.currentRecord.generationCount + 1,
12268
- activeObservations: input.reflection,
12269
- totalTokensObserved: input.currentRecord.totalTokensObserved,
12270
- observationTokenCount: input.tokenCount,
12271
- pendingMessageTokens: 0,
12272
- isReflecting: false,
12273
- isObserving: false,
12274
- isBufferingObservation: false,
12275
- isBufferingReflection: false,
12276
- lastBufferedAtTokens: 0,
12277
- lastBufferedAtTime: null,
12278
- config: input.currentRecord.config,
12279
- metadata: input.currentRecord.metadata,
12280
- observedTimezone: input.currentRecord.observedTimezone
12281
- };
12282
12283
  const tableName = getTableName$3({
12283
12284
  indexName: OM_TABLE,
12284
12285
  schemaName: getSchemaName$3(this.#schema)
12285
12286
  });
12286
- const nowStr = now.toISOString();
12287
- const lastObservedAtStr = record.lastObservedAt?.toISOString() || null;
12288
- await this.#db.client.none(`INSERT INTO ${tableName} (
12289
- id, "lookupKey", scope, "resourceId", "threadId",
12290
- "activeObservations", "activeObservationsPendingUpdate",
12291
- "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
12292
- "pendingMessageTokens", "totalTokensObserved", "observationTokenCount",
12293
- "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
12294
- "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
12295
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)`, [
12296
- id,
12297
- lookupKey,
12298
- record.scope,
12299
- record.resourceId,
12300
- record.threadId || null,
12301
- input.reflection,
12302
- null,
12303
- "reflection",
12304
- toPgJson(record.config),
12305
- input.currentRecord.generationCount + 1,
12306
- lastObservedAtStr,
12307
- lastObservedAtStr,
12308
- nowStr,
12309
- nowStr,
12310
- record.pendingMessageTokens,
12311
- Math.round(record.totalTokensObserved),
12312
- Math.round(record.observationTokenCount),
12313
- false,
12314
- false,
12315
- false,
12316
- false,
12317
- 0,
12318
- null,
12319
- record.observedTimezone || null,
12320
- record.metadata ? toPgJson(record.metadata) : null,
12321
- nowStr,
12322
- nowStr,
12323
- nowStr,
12324
- nowStr
12325
- ]);
12326
- return record;
12287
+ return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
12288
+ const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
12289
+ if (newer) return newer;
12290
+ return this.#insertReflectionGeneration(t, tableName, lookupKey, input);
12291
+ });
12327
12292
  } catch (error) {
12328
12293
  throw new MastraError({
12329
12294
  id: createStorageErrorId("PG", "CREATE_REFLECTION_GENERATION", "FAILED"),
@@ -12333,6 +12298,84 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12333
12298
  }, error);
12334
12299
  }
12335
12300
  }
12301
+ /**
12302
+ * Returns the active record when another writer already created a generation
12303
+ * after `generationCount`. Reflecting from the older generation again would
12304
+ * add a second record with the same generation.
12305
+ */
12306
+ async #getNewerOMGeneration(t, tableName, lookupKey, generationCount) {
12307
+ const latest = await this.#getLatestOMRow(t, tableName, lookupKey);
12308
+ return latest && Number(latest.generationCount) > generationCount ? this.parseOMRow(latest) : null;
12309
+ }
12310
+ async #insertReflectionGeneration(t, tableName, lookupKey, input) {
12311
+ const id = crypto.randomUUID();
12312
+ const now = /* @__PURE__ */ new Date();
12313
+ const record = {
12314
+ id,
12315
+ scope: input.currentRecord.scope,
12316
+ threadId: input.currentRecord.threadId,
12317
+ resourceId: input.currentRecord.resourceId,
12318
+ createdAt: now,
12319
+ updatedAt: now,
12320
+ lastObservedAt: input.currentRecord.lastObservedAt,
12321
+ originType: "reflection",
12322
+ generationCount: input.currentRecord.generationCount + 1,
12323
+ activeObservations: input.reflection,
12324
+ totalTokensObserved: input.currentRecord.totalTokensObserved,
12325
+ observationTokenCount: input.tokenCount,
12326
+ pendingMessageTokens: 0,
12327
+ isReflecting: false,
12328
+ isObserving: false,
12329
+ isBufferingObservation: false,
12330
+ isBufferingReflection: false,
12331
+ lastBufferedAtTokens: 0,
12332
+ lastBufferedAtTime: null,
12333
+ config: input.currentRecord.config,
12334
+ metadata: input.currentRecord.metadata,
12335
+ observedTimezone: input.currentRecord.observedTimezone
12336
+ };
12337
+ const nowStr = now.toISOString();
12338
+ const lastObservedAtStr = record.lastObservedAt?.toISOString() || null;
12339
+ await t.none(`INSERT INTO ${tableName} (
12340
+ id, "lookupKey", scope, "resourceId", "threadId",
12341
+ "activeObservations", "activeObservationsPendingUpdate",
12342
+ "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
12343
+ "pendingMessageTokens", "totalTokensObserved", "observationTokenCount",
12344
+ "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
12345
+ "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
12346
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)`, [
12347
+ id,
12348
+ lookupKey,
12349
+ record.scope,
12350
+ record.resourceId,
12351
+ record.threadId || null,
12352
+ input.reflection,
12353
+ null,
12354
+ "reflection",
12355
+ toPgJson(record.config),
12356
+ input.currentRecord.generationCount + 1,
12357
+ lastObservedAtStr,
12358
+ lastObservedAtStr,
12359
+ nowStr,
12360
+ nowStr,
12361
+ record.pendingMessageTokens,
12362
+ Math.round(record.totalTokensObserved),
12363
+ Math.round(record.observationTokenCount),
12364
+ false,
12365
+ false,
12366
+ false,
12367
+ false,
12368
+ 0,
12369
+ null,
12370
+ record.observedTimezone || null,
12371
+ record.metadata ? toPgJson(record.metadata) : null,
12372
+ nowStr,
12373
+ nowStr,
12374
+ nowStr,
12375
+ nowStr
12376
+ ]);
12377
+ return record;
12378
+ }
12336
12379
  async setReflectingFlag(id, isReflecting) {
12337
12380
  try {
12338
12381
  const tableName = getTableName$3({
@@ -12827,32 +12870,36 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12827
12870
  indexName: OM_TABLE,
12828
12871
  schemaName: getSchemaName$3(this.#schema)
12829
12872
  });
12830
- const record = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.currentRecord.id]);
12831
- if (!record) throw new MastraError({
12832
- id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
12833
- text: `Observational memory record not found: ${input.currentRecord.id}`,
12834
- domain: ErrorDomain.STORAGE,
12835
- category: ErrorCategory.THIRD_PARTY,
12836
- details: { id: input.currentRecord.id }
12837
- });
12838
- const bufferedReflection = record.bufferedReflection || "";
12839
- const reflectedLineCount = Number(record.reflectedObservationLineCount || 0);
12840
- if (!bufferedReflection) throw new MastraError({
12841
- id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
12842
- text: "No buffered reflection to swap",
12843
- domain: ErrorDomain.STORAGE,
12844
- category: ErrorCategory.USER,
12845
- details: { id: input.currentRecord.id }
12846
- });
12847
- const unreflectedContent = (record.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
12848
- const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
12849
- const newRecord = await this.createReflectionGeneration({
12850
- currentRecord: input.currentRecord,
12851
- reflection: newObservations,
12852
- tokenCount: input.tokenCount
12853
- });
12854
- const nowStr = (/* @__PURE__ */ new Date()).toISOString();
12855
- await this.#db.client.query(`UPDATE ${tableName} SET
12873
+ const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
12874
+ return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
12875
+ const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
12876
+ if (newer) return newer;
12877
+ const record = await t.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.currentRecord.id]);
12878
+ if (!record) throw new MastraError({
12879
+ id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
12880
+ text: `Observational memory record not found: ${input.currentRecord.id}`,
12881
+ domain: ErrorDomain.STORAGE,
12882
+ category: ErrorCategory.THIRD_PARTY,
12883
+ details: { id: input.currentRecord.id }
12884
+ });
12885
+ const bufferedReflection = record.bufferedReflection || "";
12886
+ const reflectedLineCount = Number(record.reflectedObservationLineCount || 0);
12887
+ if (!bufferedReflection) throw new MastraError({
12888
+ id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
12889
+ text: "No buffered reflection to swap",
12890
+ domain: ErrorDomain.STORAGE,
12891
+ category: ErrorCategory.USER,
12892
+ details: { id: input.currentRecord.id }
12893
+ });
12894
+ const unreflectedContent = (record.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
12895
+ const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
12896
+ const newRecord = await this.#insertReflectionGeneration(t, tableName, lookupKey, {
12897
+ currentRecord: input.currentRecord,
12898
+ reflection: newObservations,
12899
+ tokenCount: input.tokenCount
12900
+ });
12901
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
12902
+ await t.query(`UPDATE ${tableName} SET
12856
12903
  "bufferedReflection" = NULL,
12857
12904
  "bufferedReflectionTokens" = NULL,
12858
12905
  "bufferedReflectionInputTokens" = NULL,
@@ -12860,11 +12907,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12860
12907
  "updatedAt" = $1,
12861
12908
  "updatedAtZ" = $2
12862
12909
  WHERE id = $3`, [
12863
- nowStr,
12864
- nowStr,
12865
- input.currentRecord.id
12866
- ]);
12867
- return newRecord;
12910
+ nowStr,
12911
+ nowStr,
12912
+ input.currentRecord.id
12913
+ ]);
12914
+ return newRecord;
12915
+ });
12868
12916
  } catch (error) {
12869
12917
  if (error instanceof MastraError) throw error;
12870
12918
  throw new MastraError({