@mastra/pg 1.26.0-alpha.2 → 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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-rag-vector-databases.md +34 -0
- package/dist/index.cjs +232 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -37
- package/dist/index.js.map +1 -1
- package/dist/storage/db/constraint-utils.d.ts +11 -1
- package/dist/storage/db/constraint-utils.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/scores.d.ts +1 -0
- package/dist/storage/domains/observability/v-next/scores.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +4 -2
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +32 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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
|
|
@@ -10758,7 +10776,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
10758
10776
|
hasMore: false
|
|
10759
10777
|
};
|
|
10760
10778
|
const limitValue = perPageInput === false ? total : perPage;
|
|
10761
|
-
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
10779
|
+
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY COALESCE("${field}Z", "${field}") ${direction}, "id" ${direction} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
|
10762
10780
|
return {
|
|
10763
10781
|
threads: (await this.#db.readClient.manyOrNone(dataQuery, [
|
|
10764
10782
|
...queryParams,
|
|
@@ -10951,10 +10969,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
10951
10969
|
return messages.sort((a, b) => {
|
|
10952
10970
|
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
10953
10971
|
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
10954
|
-
|
|
10972
|
+
const idOrder = direction === "ASC" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id);
|
|
10973
|
+
if (aValue == null && bValue == null) return idOrder;
|
|
10955
10974
|
if (aValue == null) return 1;
|
|
10956
10975
|
if (bValue == null) return -1;
|
|
10957
|
-
if (aValue === bValue) return
|
|
10976
|
+
if (aValue === bValue) return idOrder;
|
|
10958
10977
|
if (typeof aValue === "number" && typeof bValue === "number") return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
10959
10978
|
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
10960
10979
|
});
|
|
@@ -11157,7 +11176,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
11157
11176
|
const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
|
|
11158
11177
|
try {
|
|
11159
11178
|
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
11160
|
-
const orderByStatement = `ORDER BY "${field}" ${direction}`;
|
|
11179
|
+
const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
|
|
11161
11180
|
const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
11162
11181
|
const tableName = getTableName$3({
|
|
11163
11182
|
indexName: TABLE_MESSAGES,
|
|
@@ -11312,7 +11331,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
11312
11331
|
const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
|
|
11313
11332
|
try {
|
|
11314
11333
|
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
11315
|
-
const orderByStatement = `ORDER BY "${field}" ${direction}`;
|
|
11334
|
+
const orderByStatement = `ORDER BY "${field}" ${direction}, "id" ${direction}`;
|
|
11316
11335
|
const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
11317
11336
|
const tableName = getTableName$3({
|
|
11318
11337
|
indexName: TABLE_MESSAGES,
|
|
@@ -14807,6 +14826,11 @@ function tableIndexes() {
|
|
|
14807
14826
|
columns: "(\"tags\")",
|
|
14808
14827
|
using: "gin"
|
|
14809
14828
|
},
|
|
14829
|
+
{
|
|
14830
|
+
name: "mastra_score_events_scoreid_cursor_idx",
|
|
14831
|
+
table: TABLE_SCORE_EVENTS,
|
|
14832
|
+
columns: "(\"scoreId\", \"cursorId\" DESC)"
|
|
14833
|
+
},
|
|
14810
14834
|
{
|
|
14811
14835
|
name: "mastra_score_events_cursor_idx",
|
|
14812
14836
|
table: TABLE_SCORE_EVENTS,
|
|
@@ -16939,13 +16963,32 @@ function pushScoreIdentity(acc, scorerId, scoreSource) {
|
|
|
16939
16963
|
acc.params.push(scoreSource);
|
|
16940
16964
|
}
|
|
16941
16965
|
}
|
|
16966
|
+
function scoreRewriteConflict(row) {
|
|
16967
|
+
return `ON CONFLICT ("scoreId", "timestamp") DO UPDATE SET ${[
|
|
16968
|
+
...Object.keys(row).filter((column) => column !== "scoreId" && column !== "timestamp").map((column) => `"${column}" = EXCLUDED."${column}"`),
|
|
16969
|
+
"\"cursorId\" = EXCLUDED.\"cursorId\"",
|
|
16970
|
+
"\"xactId\" = EXCLUDED.\"xactId\""
|
|
16971
|
+
].join(", ")}`;
|
|
16972
|
+
}
|
|
16973
|
+
function collapseExactScoreConflicts(rows) {
|
|
16974
|
+
const records = /* @__PURE__ */ new Map();
|
|
16975
|
+
for (const row of rows) {
|
|
16976
|
+
const timestamp = new Date(row.timestamp).toISOString();
|
|
16977
|
+
const key = `${String(row.scoreId)}\u0000${timestamp}`;
|
|
16978
|
+
records.delete(key);
|
|
16979
|
+
records.set(key, row);
|
|
16980
|
+
}
|
|
16981
|
+
return [...records.values()];
|
|
16982
|
+
}
|
|
16942
16983
|
async function createScore(client, schema, args) {
|
|
16943
|
-
const
|
|
16984
|
+
const row = scoreRecordToRow(args.score);
|
|
16985
|
+
const insert = buildInsert(schema, TABLE_SCORE_EVENTS, [row], scoreRewriteConflict(row));
|
|
16944
16986
|
if (insert) await client.query(insert.text, insert.values);
|
|
16945
16987
|
}
|
|
16946
16988
|
async function batchCreateScores(client, schema, args) {
|
|
16947
16989
|
if (args.scores.length === 0) return;
|
|
16948
|
-
const
|
|
16990
|
+
const rows = collapseExactScoreConflicts(args.scores.map(scoreRecordToRow));
|
|
16991
|
+
const insert = buildInsert(schema, TABLE_SCORE_EVENTS, rows, scoreRewriteConflict(rows[0]));
|
|
16949
16992
|
if (insert) await client.query(insert.text, insert.values);
|
|
16950
16993
|
}
|
|
16951
16994
|
/**
|
|
@@ -16968,6 +17011,16 @@ async function deleteScores(client, schema, args) {
|
|
|
16968
17011
|
}
|
|
16969
17012
|
await client.query(`DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`, values);
|
|
16970
17013
|
}
|
|
17014
|
+
function latestScorePredicate(table, alias = "s") {
|
|
17015
|
+
return `NOT EXISTS (
|
|
17016
|
+
SELECT 1 FROM ${table} newer
|
|
17017
|
+
WHERE newer."scoreId" = ${alias}."scoreId"
|
|
17018
|
+
AND newer."cursorId" > ${alias}."cursorId"
|
|
17019
|
+
)`;
|
|
17020
|
+
}
|
|
17021
|
+
function applyLatestScorePredicate(acc, table) {
|
|
17022
|
+
acc.conditions.push(latestScorePredicate(table));
|
|
17023
|
+
}
|
|
16971
17024
|
async function listScores(client, schema, args) {
|
|
16972
17025
|
const { mode, filters, pagination, orderBy, after, limit } = listScoresArgsSchema.parse(args);
|
|
16973
17026
|
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
@@ -16978,28 +17031,50 @@ async function listScores(client, schema, args) {
|
|
|
16978
17031
|
return listScoresPage(client, table, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
|
|
16979
17032
|
}
|
|
16980
17033
|
async function getScoreById(client, schema, scoreId) {
|
|
17034
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
16981
17035
|
const row = await client.oneOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
|
|
16982
|
-
FROM ${
|
|
17036
|
+
FROM ${table}
|
|
16983
17037
|
WHERE "scoreId" = $1
|
|
16984
|
-
ORDER BY "
|
|
17038
|
+
ORDER BY "cursorId" DESC
|
|
16985
17039
|
LIMIT 1`, [scoreId]);
|
|
16986
17040
|
return row ? rowToScoreRecord(row) : null;
|
|
16987
17041
|
}
|
|
16988
17042
|
async function listScoresPage(client, table, filters, page, perPage, orderField, orderDir) {
|
|
16989
|
-
|
|
17043
|
+
const acc = newFilterAccumulator();
|
|
17044
|
+
applyScoreFilters(acc, filters);
|
|
17045
|
+
applyLatestScorePredicate(acc, table);
|
|
17046
|
+
const whereClause = whereOrEmpty(acc);
|
|
17047
|
+
const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${table} s ${whereClause}`, acc.params);
|
|
17048
|
+
const total = Number(countRow?.count ?? 0);
|
|
17049
|
+
let scores = [];
|
|
17050
|
+
if (total > 0) {
|
|
17051
|
+
const safeOrderField = parseSqlIdentifier(orderField, "order field");
|
|
17052
|
+
scores = (await client.manyOrNone(`SELECT ${SCORE_SELECT_COLUMNS}
|
|
17053
|
+
FROM ${table} s
|
|
17054
|
+
${whereClause}
|
|
17055
|
+
ORDER BY "${safeOrderField}" ${orderDir}, "cursorId" ${orderDir}
|
|
17056
|
+
LIMIT $${acc.next++} OFFSET $${acc.next++}`, [
|
|
17057
|
+
...acc.params,
|
|
17058
|
+
perPage,
|
|
17059
|
+
page * perPage
|
|
17060
|
+
])).map(rowToScoreRecord);
|
|
17061
|
+
}
|
|
17062
|
+
const deltaCursor = deltaPollingFeatureEnabled() ? await readSignalStreamHeadCursor({
|
|
16990
17063
|
client,
|
|
16991
17064
|
table,
|
|
16992
17065
|
filters,
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
16997
|
-
|
|
16998
|
-
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
|
|
17066
|
+
applyFilters: applyScoreFilters
|
|
17067
|
+
}) : void 0;
|
|
17068
|
+
return {
|
|
17069
|
+
scores,
|
|
17070
|
+
pagination: {
|
|
17071
|
+
total,
|
|
17072
|
+
page,
|
|
17073
|
+
perPage,
|
|
17074
|
+
hasMore: (page + 1) * perPage < total
|
|
17075
|
+
},
|
|
17076
|
+
...deltaCursor !== void 0 ? { deltaCursor } : {}
|
|
17077
|
+
};
|
|
17003
17078
|
}
|
|
17004
17079
|
async function listScoresDelta(client, table, filters, after, limit) {
|
|
17005
17080
|
return listSignalDelta({
|
|
@@ -17015,12 +17090,14 @@ async function listScoresDelta(client, table, filters, after, limit) {
|
|
|
17015
17090
|
});
|
|
17016
17091
|
}
|
|
17017
17092
|
async function runScoreAggregateQuery(client, schema, args, filters) {
|
|
17093
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17018
17094
|
const acc = newFilterAccumulator();
|
|
17019
17095
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17020
17096
|
applyScoreFilters(acc, filters);
|
|
17097
|
+
applyLatestScorePredicate(acc, table);
|
|
17021
17098
|
const sql = `
|
|
17022
17099
|
SELECT ${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17023
|
-
FROM ${
|
|
17100
|
+
FROM ${table} s
|
|
17024
17101
|
${whereOrEmpty(acc)}
|
|
17025
17102
|
`;
|
|
17026
17103
|
const row = await client.oneOrNone(sql, acc.params);
|
|
@@ -17052,10 +17129,12 @@ async function getScoreBreakdown(client, schema, args) {
|
|
|
17052
17129
|
});
|
|
17053
17130
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17054
17131
|
applyScoreFilters(acc, args.filters);
|
|
17132
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17133
|
+
applyLatestScorePredicate(acc, table);
|
|
17055
17134
|
const sql = `
|
|
17056
17135
|
SELECT ${resolved.map((e) => e.selectSql).join(", ")},
|
|
17057
17136
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17058
|
-
FROM ${
|
|
17137
|
+
FROM ${table} s
|
|
17059
17138
|
${whereOrEmpty(acc)}
|
|
17060
17139
|
GROUP BY ${resolved.map((e) => e.alias).join(", ")}
|
|
17061
17140
|
ORDER BY "value" DESC NULLS LAST
|
|
@@ -17075,11 +17154,13 @@ async function getScoreTimeSeries(client, schema, args) {
|
|
|
17075
17154
|
});
|
|
17076
17155
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17077
17156
|
applyScoreFilters(acc, args.filters);
|
|
17157
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17158
|
+
applyLatestScorePredicate(acc, table);
|
|
17078
17159
|
const sql = `
|
|
17079
17160
|
SELECT ${bucket} AS bucket,
|
|
17080
17161
|
${resolved.map((e) => e.selectSql).join(", ")},
|
|
17081
17162
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17082
|
-
FROM ${
|
|
17163
|
+
FROM ${table} s
|
|
17083
17164
|
${whereOrEmpty(acc)}
|
|
17084
17165
|
GROUP BY bucket, ${resolved.map((e) => e.alias).join(", ")}
|
|
17085
17166
|
ORDER BY bucket
|
|
@@ -17097,10 +17178,12 @@ async function getScoreTimeSeries(client, schema, args) {
|
|
|
17097
17178
|
const acc = newFilterAccumulator();
|
|
17098
17179
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17099
17180
|
applyScoreFilters(acc, args.filters);
|
|
17181
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17182
|
+
applyLatestScorePredicate(acc, table);
|
|
17100
17183
|
const sql = `
|
|
17101
17184
|
SELECT ${bucket} AS bucket,
|
|
17102
17185
|
${aggregationSql(args.aggregation, "\"score\"")} AS "value"
|
|
17103
|
-
FROM ${
|
|
17186
|
+
FROM ${table} s
|
|
17104
17187
|
${whereOrEmpty(acc)}
|
|
17105
17188
|
GROUP BY bucket
|
|
17106
17189
|
ORDER BY bucket
|
|
@@ -17120,9 +17203,11 @@ async function getScorePercentiles(client, schema, args) {
|
|
|
17120
17203
|
const acc = newFilterAccumulator();
|
|
17121
17204
|
pushScoreIdentity(acc, args.scorerId, args.scoreSource);
|
|
17122
17205
|
applyScoreFilters(acc, args.filters);
|
|
17206
|
+
const table = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17207
|
+
applyLatestScorePredicate(acc, table);
|
|
17123
17208
|
const sql = `
|
|
17124
17209
|
SELECT ${bucket} AS bucket, ${percentileSelectSql(args.percentiles, "\"score\"")}
|
|
17125
|
-
FROM ${
|
|
17210
|
+
FROM ${table} s
|
|
17126
17211
|
${whereOrEmpty(acc)}
|
|
17127
17212
|
GROUP BY bucket
|
|
17128
17213
|
ORDER BY bucket
|
|
@@ -17321,13 +17406,6 @@ function latestSpanPredicate$1(spanTable) {
|
|
|
17321
17406
|
AND (newer."isPending" < s."isPending" OR (newer."isPending" = s."isPending" AND newer."cursorId" > s."cursorId"))
|
|
17322
17407
|
)`;
|
|
17323
17408
|
}
|
|
17324
|
-
function latestScorePredicate(scoreTable) {
|
|
17325
|
-
return `NOT EXISTS (
|
|
17326
|
-
SELECT 1 FROM ${scoreTable} newer
|
|
17327
|
-
WHERE newer."scoreId" = s."scoreId"
|
|
17328
|
-
AND newer."cursorId" > s."cursorId"
|
|
17329
|
-
)`;
|
|
17330
|
-
}
|
|
17331
17409
|
function latestFeedbackPredicate(feedbackTable) {
|
|
17332
17410
|
return `NOT EXISTS (
|
|
17333
17411
|
SELECT 1 FROM ${feedbackTable} newer
|
|
@@ -17497,7 +17575,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections) {
|
|
|
17497
17575
|
values
|
|
17498
17576
|
};
|
|
17499
17577
|
}
|
|
17500
|
-
function compilePostgresTraceQuery(schema, plan) {
|
|
17578
|
+
function compilePostgresTraceQuery(schema, plan, mode = "data") {
|
|
17501
17579
|
const { ctes, values } = compilePostgresTraceScope(schema, plan, collectRelationCollections(plan.where));
|
|
17502
17580
|
let predicateSql = "TRUE";
|
|
17503
17581
|
if (plan.where) {
|
|
@@ -17511,6 +17589,12 @@ function compilePostgresTraceQuery(schema, plan) {
|
|
|
17511
17589
|
WHERE ${predicateSql}
|
|
17512
17590
|
)`);
|
|
17513
17591
|
const candidates = `WITH ${ctes.join(",\n")}`;
|
|
17592
|
+
if (plan.paginationMode === "page" && mode === "count") return {
|
|
17593
|
+
text: `${candidates}
|
|
17594
|
+
SELECT COUNT(*)::text AS count
|
|
17595
|
+
FROM candidates`,
|
|
17596
|
+
values
|
|
17597
|
+
};
|
|
17514
17598
|
if (plan.result === "groups") {
|
|
17515
17599
|
const pageCondition = plan.cursor ? `AND "threadId" > $${values.length + 1}` : "";
|
|
17516
17600
|
if (plan.cursor) values.push(plan.cursor.threadId);
|
|
@@ -17528,6 +17612,17 @@ LIMIT $${values.length}`,
|
|
|
17528
17612
|
}
|
|
17529
17613
|
const orderField = plan.orderBy.field === "startedAt" ? "\"startedAt\"" : "\"endedAt\"";
|
|
17530
17614
|
const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
|
|
17615
|
+
if (plan.paginationMode === "page") {
|
|
17616
|
+
values.push(plan.perPage, plan.page * plan.perPage);
|
|
17617
|
+
return {
|
|
17618
|
+
text: `${candidates}
|
|
17619
|
+
SELECT *
|
|
17620
|
+
FROM candidates
|
|
17621
|
+
ORDER BY ${orderField} ${direction}, "traceId" ASC
|
|
17622
|
+
LIMIT $${values.length - 1} OFFSET $${values.length}`,
|
|
17623
|
+
values
|
|
17624
|
+
};
|
|
17625
|
+
}
|
|
17531
17626
|
let pageCondition = "";
|
|
17532
17627
|
if (plan.cursor) {
|
|
17533
17628
|
const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
|
|
@@ -17674,10 +17769,11 @@ function isPostgresResourceLimit(error) {
|
|
|
17674
17769
|
const candidate = error;
|
|
17675
17770
|
return candidate.code === "53200" || candidate.code === "53400";
|
|
17676
17771
|
}
|
|
17677
|
-
async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
|
|
17772
|
+
async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute, options = {}) {
|
|
17678
17773
|
const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
17679
17774
|
try {
|
|
17680
17775
|
return await client.tx(async (transaction) => {
|
|
17776
|
+
if (options.repeatableRead) await transaction.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
|
|
17681
17777
|
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${resolvedTimeoutMs}ms`]);
|
|
17682
17778
|
return execute(transaction);
|
|
17683
17779
|
});
|
|
@@ -17711,6 +17807,50 @@ async function getTraceQueryValues(client, schema, plan, timeoutMs) {
|
|
|
17711
17807
|
});
|
|
17712
17808
|
}
|
|
17713
17809
|
async function queryTraces(client, schema, plan, timeoutMs) {
|
|
17810
|
+
if (plan.paginationMode === "page") {
|
|
17811
|
+
const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
17812
|
+
const deadline = performance.now() + resolvedTimeoutMs;
|
|
17813
|
+
const countQuery = compilePostgresTraceQuery(schema, plan, "count");
|
|
17814
|
+
const dataQuery = compilePostgresTraceQuery(schema, plan);
|
|
17815
|
+
const { total, rows } = await runWithPostgresTraceQueryTimeout(client, resolvedTimeoutMs, async (transaction) => {
|
|
17816
|
+
const countRows = await transaction.any(countQuery.text, countQuery.values);
|
|
17817
|
+
const remainingTimeoutMs = Math.floor(deadline - performance.now());
|
|
17818
|
+
if (remainingTimeoutMs <= 0) throw new coreStorage.TraceQueryExecutionError();
|
|
17819
|
+
await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
|
|
17820
|
+
const rows = await transaction.any(dataQuery.text, dataQuery.values);
|
|
17821
|
+
return {
|
|
17822
|
+
total: Number(countRows[0]?.count ?? 0),
|
|
17823
|
+
rows
|
|
17824
|
+
};
|
|
17825
|
+
}, { repeatableRead: true });
|
|
17826
|
+
const traces = rows.map((row) => ({
|
|
17827
|
+
traceId: String(row.traceId),
|
|
17828
|
+
rootSpanId: String(row.rootSpanId),
|
|
17829
|
+
name: row.name,
|
|
17830
|
+
entityId: row.entityId ?? null,
|
|
17831
|
+
parentSpanId: row.parentSpanId ?? null,
|
|
17832
|
+
createdAt: asIsoTimestamp$1(row.startedAt),
|
|
17833
|
+
metadata: row.metadata ?? null,
|
|
17834
|
+
inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
|
|
17835
|
+
threadId: row.threadId == null ? null : String(row.threadId),
|
|
17836
|
+
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
17837
|
+
startedAt: asIsoTimestamp$1(row.startedAt),
|
|
17838
|
+
endedAt: asIsoTimestamp$1(row.endedAt),
|
|
17839
|
+
entityName: row.entityName == null ? null : String(row.entityName),
|
|
17840
|
+
entityType: row.entityType == null ? null : String(row.entityType),
|
|
17841
|
+
environment: row.environment == null ? null : String(row.environment),
|
|
17842
|
+
status: row.status
|
|
17843
|
+
}));
|
|
17844
|
+
return coreStorage.traceQueryResponseSchema.parse({
|
|
17845
|
+
traces,
|
|
17846
|
+
pagination: {
|
|
17847
|
+
total,
|
|
17848
|
+
page: plan.page,
|
|
17849
|
+
perPage: plan.perPage,
|
|
17850
|
+
hasMore: (plan.page + 1) * plan.perPage < total
|
|
17851
|
+
}
|
|
17852
|
+
});
|
|
17853
|
+
}
|
|
17714
17854
|
const query = compilePostgresTraceQuery(schema, plan);
|
|
17715
17855
|
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17716
17856
|
const visibleRows = rows.slice(0, plan.limit);
|
|
@@ -22171,6 +22311,7 @@ const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_cre
|
|
|
22171
22311
|
* Schema-prefixed name of the status index, lowercased and truncated the same way Postgres
|
|
22172
22312
|
* stores it, so the init snapshot's index set answers "does it exist?" without a probe or a
|
|
22173
22313
|
* no-op `CREATE INDEX` (schema-prefixed names routinely exceed the 63-byte limit).
|
|
22314
|
+
* Exported for tests.
|
|
22174
22315
|
*/
|
|
22175
22316
|
function workflowSnapshotStatusIndexName(schemaName) {
|
|
22176
22317
|
return buildConstraintName({
|
|
@@ -22188,6 +22329,48 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
|
|
|
22188
22329
|
schemaName: getSchemaName(schemaName)
|
|
22189
22330
|
})} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
|
|
22190
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
|
+
}
|
|
22191
22374
|
var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
22192
22375
|
#db;
|
|
22193
22376
|
#schema;
|
|
@@ -22260,6 +22443,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
22260
22443
|
}));
|
|
22261
22444
|
for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
|
|
22262
22445
|
statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
|
|
22446
|
+
statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
|
|
22263
22447
|
return statements;
|
|
22264
22448
|
}
|
|
22265
22449
|
/**
|
|
@@ -22286,6 +22470,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
22286
22470
|
} catch (error) {
|
|
22287
22471
|
this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
|
|
22288
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
|
+
}
|
|
22289
22479
|
}
|
|
22290
22480
|
async init() {
|
|
22291
22481
|
await this.#db.createTable({
|
|
@@ -22566,7 +22756,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
22566
22756
|
}, error);
|
|
22567
22757
|
}
|
|
22568
22758
|
}
|
|
22569
|
-
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, status } = {}) {
|
|
22759
|
+
async listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId, threadId, status } = {}) {
|
|
22570
22760
|
try {
|
|
22571
22761
|
const conditions = [];
|
|
22572
22762
|
const values = [];
|
|
@@ -22587,6 +22777,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
|
|
|
22587
22777
|
values.push(resourceId);
|
|
22588
22778
|
paramIndex++;
|
|
22589
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.`);
|
|
22590
22785
|
if (fromDate) {
|
|
22591
22786
|
conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
22592
22787
|
values.push(fromDate);
|