@mastra/pg 1.26.0-alpha.3 → 1.26.0-alpha.4

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
@@ -2336,6 +2336,8 @@ var RoutingDbClient = class {
2336
2336
  return this.active.tx(callback);
2337
2337
  }
2338
2338
  };
2339
+ /** Bytes reserved for the `_xxxxxxxx` collision suffix when hashWhenTruncated applies. */
2340
+ const TRUNCATION_HASH_SUFFIX_LENGTH = 9;
2339
2341
  function truncateIdentifier(value, maxLength = 63) {
2340
2342
  if (maxLength <= 0) return "";
2341
2343
  if (Buffer.byteLength(value, "utf-8") <= maxLength) return value;
@@ -2356,9 +2358,25 @@ function truncateIdentifier(value, maxLength = 63) {
2356
2358
  * in system catalogs (pg_constraint.conname, pg_indexes.indexname, etc.).
2357
2359
  * Without this normalisation, runtime lookups that compare a mixed-case name
2358
2360
  * against the catalog would silently fail.
2361
+ *
2362
+ * With `hashWhenTruncated`, a name that exceeds the limit is truncated further
2363
+ * to make room for `_` + 8 hex chars of the full name's sha256. Plain
2364
+ * truncation cuts the tail, so two names sharing a long `<schema>_<prefix>`
2365
+ * collapse to the same identifier and `CREATE INDEX IF NOT EXISTS` (which
2366
+ * matches by name only) silently skips the second one. The suffix is
2367
+ * deterministic, so creation, warm-init snapshot checks, and DDL export all
2368
+ * agree on the same name. Opt-in because renaming already-released constraint
2369
+ * names would orphan the existing objects in deployed catalogs.
2359
2370
  */
2360
- function buildConstraintName({ baseName, schemaName, maxLength = 63 }) {
2361
- return truncateIdentifier(`${schemaName ? `${schemaName}_` : ""}${baseName}`.toLowerCase(), maxLength);
2371
+ function buildConstraintName({ baseName, schemaName, maxLength = 63, hashWhenTruncated = false }) {
2372
+ const fullName = `${schemaName ? `${schemaName}_` : ""}${baseName}`.toLowerCase();
2373
+ if (hashWhenTruncated && Buffer.byteLength(fullName, "utf-8") > maxLength) {
2374
+ const suffixLength = Math.min(TRUNCATION_HASH_SUFFIX_LENGTH, maxLength);
2375
+ if (suffixLength < 2) return truncateIdentifier(fullName, maxLength);
2376
+ const hash = createHash("sha256").update(fullName).digest("hex").slice(0, suffixLength - 1);
2377
+ return `${truncateIdentifier(fullName, maxLength - suffixLength)}_${hash}`;
2378
+ }
2379
+ return truncateIdentifier(fullName, maxLength);
2362
2380
  }
2363
2381
  //#endregion
2364
2382
  //#region src/storage/db/pg-errors.ts
@@ -22293,6 +22311,7 @@ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_cre
22293
22311
  * Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
22294
22312
  * stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
22295
22313
  * no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
22314
+ * Exported for tests.
22296
22315
  */
22297
22316
  function workflowSnapshotStatusIndexName(schemaName) {
22298
22317
  return buildConstraintName({
@@ -22310,6 +22329,48 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
22310
22329
  schemaName: getSchemaName(schemaName)
22311
22330
  })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
22312
22331
  }
22332
+ /** Base name (before any schema prefix) of the expression index backing the threadId filter. */
22333
+ const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
22334
+ /**
22335
+ * Schema-prefixed name of the threadId index (see workflowSnapshotStatusIndexName).
22336
+ *
22337
+ * Unlike the status index, truncation appends a collision hash: both index names share the
22338
+ * long `<schema>_mastra_workflow_snapshot_` prefix, so with a schema name of 37+ bytes plain
22339
+ * truncation collapses them to the same 63-byte identifier and `CREATE INDEX IF NOT EXISTS`
22340
+ * silently skips this index. The status index keeps plain truncation because its truncated
22341
+ * name already exists in deployed catalogs; this index is new and free to adopt the rule.
22342
+ * Exported for tests.
22343
+ */
22344
+ function workflowSnapshotThreadIdIndexName(schemaName) {
22345
+ return buildConstraintName({
22346
+ baseName: WORKFLOW_SNAPSHOT_THREAD_ID_INDEX,
22347
+ schemaName: schemaName && schemaName !== "public" ? schemaName : void 0,
22348
+ hashWhenTruncated: true
22349
+ });
22350
+ }
22351
+ /**
22352
+ * Expression extracting the thread id embedded in a snapshot (jsonb columns only). Mirrors
22353
+ * the canonical extraction in `@mastra/core` (`getSnapshotMemoryInfo`), which reads one of
22354
+ * two layouts:
22355
+ * 1. agentic-loop: `context.<suspended step>.suspendPayload.__streamState.messageList.memoryInfo.threadId`
22356
+ * 2. durable loop: `context.input.messageListState.memoryInfo.threadId`
22357
+ *
22358
+ * `jsonb_path_query_first(jsonb, jsonpath)` is IMMUTABLE, so the expression is valid in an
22359
+ * expression index. The WHERE clause in listWorkflowRuns() must use this exact expression
22360
+ * text so the planner can match it against the index. If the snapshot layout changes in
22361
+ * core, this expression must be updated in lockstep or it will wrongly exclude rows.
22362
+ */
22363
+ const WORKFLOW_SNAPSHOT_THREAD_ID_EXPR = `COALESCE(jsonb_path_query_first(snapshot, '$.context.* ? (@.status == "suspended").suspendPayload.__streamState.messageList.memoryInfo.threadId') #>> '{}', snapshot #>> '{context,input,messageListState,memoryInfo,threadId}')`;
22364
+ /**
22365
+ * Expression index on the snapshot-embedded thread id so listWorkflowRuns() threadId filters
22366
+ * (Agent.listSuspendedRuns) can use an index instead of detoasting every snapshot.
22367
+ */
22368
+ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName) {
22369
+ return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
22370
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
22371
+ schemaName: getSchemaName(schemaName)
22372
+ })} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
22373
+ }
22313
22374
  var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22314
22375
  #db;
22315
22376
  #schema;
@@ -22382,6 +22443,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22382
22443
  }));
22383
22444
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
22384
22445
  statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
22446
+ statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
22385
22447
  return statements;
22386
22448
  }
22387
22449
  /**
@@ -22408,6 +22470,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22408
22470
  } catch (error) {
22409
22471
  this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
22410
22472
  }
22473
+ const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
22474
+ try {
22475
+ await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema));
22476
+ } catch (error) {
22477
+ this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
22478
+ }
22411
22479
  }
22412
22480
  async init() {
22413
22481
  await this.#db.createTable({
@@ -22688,7 +22756,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22688
22756
  }, error);
22689
22757
  }
22690
22758
  }
22691
- async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, status } = {}) {
22759
+ async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status } = {}) {
22692
22760
  try {
22693
22761
  const conditions = [];
22694
22762
  const values = [];
@@ -22709,6 +22777,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
22709
22777
  values.push(resourceId);
22710
22778
  paramIndex++;
22711
22779
  } else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
22780
+ if (threadId) if (await this.#db.getColumnType(TABLE_WORKFLOW_SNAPSHOT, "snapshot") === "jsonb") {
22781
+ conditions.push(`${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR} = $${paramIndex}`);
22782
+ values.push(threadId);
22783
+ paramIndex++;
22784
+ } else this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] snapshot column is not jsonb. Skipping threadId filter.`);
22712
22785
  if (fromDate) {
22713
22786
  conditions.push(`"createdAt" >= $${paramIndex}`);
22714
22787
  values.push(fromDate);