@mastra/pg 1.25.1-alpha.0 → 1.26.0-alpha.2
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-storage-retention.md +56 -4
- package/dist/index.cjs +138 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +139 -128
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +4 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +5 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +5 -58
- package/dist/storage/retention.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/docs/SKILL.md
CHANGED
|
@@ -6,11 +6,37 @@
|
|
|
6
6
|
|
|
7
7
|
Because storage grows without bound by default, Mastra provides an opt-in, age-based retention system. Declare per-table `maxAge` policies in the `retention` config, then call `storage.prune()` to delete rows older than their configured age. Unconfigured data is kept forever, so behavior doesn't change until you opt in.
|
|
8
8
|
|
|
9
|
-
`prune()` deletes rows.
|
|
9
|
+
`prune()` deletes rows in bounded batches. Runs are resumable and cancellable, so you can limit how much work each maintenance window performs. Pruning doesn't reclaim disk space by itself. Use the database-specific maintenance guidance below when you need to return freed space to the operating system.
|
|
10
10
|
|
|
11
11
|
Retention covers **growth tables** only: tables that accumulate rows unbounded as a side effect of normal operation (conversation history, telemetry, job and run records, schedule fire history, event feeds). User-authored artifacts and config (agents, skills, workspaces, prompt blocks, datasets, schedule definitions, channel installations, and so on) grow with user intent and are edited or deleted explicitly, so they're not valid retention keys.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Storage adapters use the shared core retention contract for `prune()`, or a database-native mechanism when that better matches the backend.
|
|
14
|
+
|
|
15
|
+
| Adapter | Mechanism | Retention support |
|
|
16
|
+
| -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| libSQL | `prune()` | All supported growth domains |
|
|
18
|
+
| PostgreSQL | `prune()` | All supported growth domains. V-next observability drops expired partitions or chunks |
|
|
19
|
+
| MongoDB | `prune()` or native TTL | All supported growth domains. Native TTL indexes are also available |
|
|
20
|
+
| DuckDB | `prune()` | Observability spans, metrics, logs, scores, and feedback |
|
|
21
|
+
| MySQL | `prune()` | Observability spans |
|
|
22
|
+
| Microsoft SQL Server | `prune()` | Observability spans |
|
|
23
|
+
| Oracle Database | `prune()` | Observability spans and logs |
|
|
24
|
+
| Amazon Aurora DSQL | `prune()` | Observability spans |
|
|
25
|
+
| Google Cloud Spanner | `prune()` | Observability spans, plus metrics when metrics storage is enabled |
|
|
26
|
+
| ClickHouse | Native TTL | Observability spans, metrics, logs, scores, and feedback. When all five signals have finite retention, deletion-request records expire after the longest signal retention plus 30 days |
|
|
27
|
+
|
|
28
|
+
## Storage-specific maintenance
|
|
29
|
+
|
|
30
|
+
| Adapter | Maintenance guidance |
|
|
31
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| SQLite and libSQL | Freed pages are reused by future writes, which stops the database file from growing. Reclaiming disk space requires database-level maintenance. |
|
|
33
|
+
| DuckDB | For file-backed stores, run `CHECKPOINT` after pruning to reclaim deleted rows in storage. DuckDB's `VACUUM` doesn't reclaim deleted rows. |
|
|
34
|
+
|
|
35
|
+
## Schedule pruning
|
|
36
|
+
|
|
37
|
+
Run `prune()` from a scheduler or maintenance worker, not from application startup or shutdown hooks. For deployments that share a database, prefer a single active scheduler or worker for pruning.
|
|
38
|
+
|
|
39
|
+
Prefer lower-traffic periods when pruning large tables. Use `maxBatches`, `maxRows`, and `pauseMs` to bound each run, and pass an `AbortSignal` when the maintenance process needs to stop promptly. These recommendations apply to adapters that expose `prune()`. ClickHouse applies its native time to live (TTL) policy within the database.
|
|
14
40
|
|
|
15
41
|
## Usage example
|
|
16
42
|
|
|
@@ -94,7 +120,8 @@ Each domain specifies its age-prunable tables and the timestamp column that anch
|
|
|
94
120
|
> - Experiments prune as whole units: an aged experiment's result rows are deleted together with it (results cascade with their parent), so a run is never left partially deleted. Retention doesn't have a separate `results` key.
|
|
95
121
|
> - For `schedules`, the growth table is the fire history (`schedule_triggers`, one row per fire): schedule definitions are config and aren't pruned.
|
|
96
122
|
> - On PostgreSQL, timestamp anchors use the timezone-aware mirror columns (for example `createdAtZ`, `completedAtZ`).
|
|
97
|
-
> -
|
|
123
|
+
> - DuckDB observability stores append-only events for all five signals. Its `spans` policy uses the event `timestamp` column rather than `startedAt`.
|
|
124
|
+
> - LibSQL and PostgreSQL support all domains above except `harness`, which PostgreSQL doesn't implement. MongoDB supports all except `threadState` and `harness`. DuckDB, MySQL, Microsoft SQL Server, Oracle Database, Amazon Aurora DSQL, and Google Cloud Spanner currently support retention only in their `observability` domains, with the signal coverage shown in the support matrix.
|
|
98
125
|
> - The v-next PostgreSQL observability domain stores signal events in day-partitioned tables (`spans`, `metrics`, `logs`, `scores`, `feedback`). For it, `prune()` drops whole day partitions (or TimescaleDB chunks) that are entirely older than the cutoff instead of deleting rows: effective level of detail is one day, and a partition is only dropped once its entire day is past `maxAge`. `PruneResult.deleted` reports the number of rows in the dropped partitions.
|
|
99
126
|
|
|
100
127
|
## Methods
|
|
@@ -109,7 +136,7 @@ Deletes rows older than their configured `maxAge` across every domain that has a
|
|
|
109
136
|
|
|
110
137
|
Pass `options.retention` to replace the configured policies for that call only: for example to skip a domain (keep chat history) or prune more aggressively than the standing config. The store's configured `retention` is unchanged.
|
|
111
138
|
|
|
112
|
-
|
|
139
|
+
Adapters that use anchor-column indexes create them lazily on the first `prune()` call for each table with a policy (never at `init()`) so deployments that don't configure retention pay no extra index write or disk overhead. The first prune of an existing large table pays a one-time index build. Subsequent prunes reuse the index. DuckDB uses its built-in zone maps instead of creating retention indexes.
|
|
113
140
|
|
|
114
141
|
```typescript
|
|
115
142
|
const results = await storage.prune({
|
|
@@ -177,6 +204,31 @@ async function retentionTick() {
|
|
|
177
204
|
|
|
178
205
|
You can also cancel a long-running prune with an `AbortSignal`: the loop stops between batches and returns partial results with `done: false`, so the next run resumes cleanly.
|
|
179
206
|
|
|
207
|
+
## ClickHouse native TTL
|
|
208
|
+
|
|
209
|
+
ClickHouse observability storage uses native table TTLs instead of `prune()`. Configure retention as days per signal. `init()` applies the TTLs to new and existing tables and skips `ALTER TABLE` statements when the configured TTL is already present.
|
|
210
|
+
|
|
211
|
+
For deployments that need to update TTL configuration without running the full initialization path, call `applyRetention()` on the v-next observability store:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { ObservabilityStorageClickhouseVNext } from '@mastra/clickhouse'
|
|
215
|
+
|
|
216
|
+
const observability = new ObservabilityStorageClickhouseVNext({
|
|
217
|
+
client,
|
|
218
|
+
retention: {
|
|
219
|
+
tracing: 30,
|
|
220
|
+
logs: 7,
|
|
221
|
+
metrics: 14,
|
|
222
|
+
scores: 90,
|
|
223
|
+
feedback: 60,
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await observability.applyRetention()
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Deletion requests are retained long enough to keep enforcing erasure after signal rows expire. Mastra applies a TTL to `mastra_deletion_requests` only when tracing, logs, metrics, scores, and feedback all have finite retention. The deletion-request TTL is the longest of those periods plus 30 days. For example, if score retention is the longest period at 90 days, deletion requests expire after 120 days. When any signal is unbounded, deletion requests remain unbounded because trace deletion requests cover rows across all five signals.
|
|
231
|
+
|
|
180
232
|
## MongoDB TTL indexes (alternative to prune)
|
|
181
233
|
|
|
182
234
|
MongoDB offers native [TTL (Time-To-Live) indexes](https://www.mongodb.com/docs/manual/core/index-ttl/) that automatically delete expired documents without requiring manual `prune()` calls. This is a database-level feature that runs as a background thread.
|
package/dist/index.cjs
CHANGED
|
@@ -4927,137 +4927,31 @@ var AgentsPG = class AgentsPG extends _mastra_core_storage.AgentsStorage {
|
|
|
4927
4927
|
};
|
|
4928
4928
|
//#endregion
|
|
4929
4929
|
//#region src/storage/retention.ts
|
|
4930
|
-
const DEFAULT_BATCH_SIZE = 1e3;
|
|
4931
|
-
async function sleep(ms, signal) {
|
|
4932
|
-
if (ms <= 0 || signal?.aborted) return;
|
|
4933
|
-
await new Promise((resolve) => {
|
|
4934
|
-
const timer = setTimeout(() => {
|
|
4935
|
-
signal?.removeEventListener("abort", onAbort);
|
|
4936
|
-
resolve();
|
|
4937
|
-
}, ms);
|
|
4938
|
-
const onAbort = () => {
|
|
4939
|
-
clearTimeout(timer);
|
|
4940
|
-
resolve();
|
|
4941
|
-
};
|
|
4942
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4943
|
-
});
|
|
4944
|
-
}
|
|
4945
|
-
/**
|
|
4946
|
-
* Convert a policy's `maxAge` into a cutoff bound matching the anchor's storage
|
|
4947
|
-
* type: a `Date` for `timestamptz` columns (pg compares timezone-aware), or a
|
|
4948
|
-
* raw millisecond number for `bigint` epoch-ms columns.
|
|
4949
|
-
*/
|
|
4950
4930
|
function cutoffFor(policy, anchorType, now = Date.now()) {
|
|
4951
|
-
const cutoffMs =
|
|
4931
|
+
const cutoffMs = (0, _mastra_core_storage.retentionCutoffMs)(policy, now);
|
|
4952
4932
|
return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs);
|
|
4953
4933
|
}
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
};
|
|
4969
|
-
if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return {
|
|
4970
|
-
deleted,
|
|
4971
|
-
done: false
|
|
4972
|
-
};
|
|
4973
|
-
let limit = batchSize;
|
|
4974
|
-
if (options?.maxRows !== void 0) {
|
|
4975
|
-
const remaining = options.maxRows - deleted;
|
|
4976
|
-
if (remaining <= 0) return {
|
|
4977
|
-
deleted,
|
|
4978
|
-
done: false
|
|
4979
|
-
};
|
|
4980
|
-
limit = Math.min(limit, remaining);
|
|
4981
|
-
}
|
|
4982
|
-
const affected = await deleteBatch(limit);
|
|
4983
|
-
deleted += affected;
|
|
4984
|
-
batches += 1;
|
|
4985
|
-
if (affected < limit) return {
|
|
4986
|
-
deleted,
|
|
4987
|
-
done: true
|
|
4988
|
-
};
|
|
4989
|
-
if (options?.pauseMs) await sleep(options.pauseMs, options.signal);
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
/**
|
|
4993
|
-
* Runs the bounded, batched, cancellable delete loop for a set of tables in the
|
|
4994
|
-
* given order (callers pass children before parents for cascade-safe pruning),
|
|
4995
|
-
* and returns one {@link PruneResult} per table.
|
|
4996
|
-
*
|
|
4997
|
-
* The loop:
|
|
4998
|
-
* - deletes in chunks of `batchSize` (default 1000), each its own statement;
|
|
4999
|
-
* - stops a table's loop when a batch deletes fewer rows than requested (drained),
|
|
5000
|
-
* or when `maxBatches`/`maxRows` is hit, or the `signal` aborts — the latter
|
|
5001
|
-
* three leave `done: false` so the caller can resume;
|
|
5002
|
-
* - pauses `pauseMs` between batches when set, to avoid starving live traffic.
|
|
5003
|
-
*
|
|
5004
|
-
* `prune()` only deletes rows; it never reclaims disk. PostgreSQL reuses freed
|
|
5005
|
-
* space (dead tuples) via autovacuum on subsequent writes, so tables stop
|
|
5006
|
-
* growing. Returning disk to the OS (e.g. `VACUUM FULL`) is left to the operator.
|
|
5007
|
-
*/
|
|
5008
|
-
async function runPrune({ db, domain, targets, options }) {
|
|
5009
|
-
const results = [];
|
|
5010
|
-
const now = Date.now();
|
|
5011
|
-
for (const target of targets) {
|
|
5012
|
-
if (options?.signal?.aborted) {
|
|
5013
|
-
results.push({
|
|
5014
|
-
domain,
|
|
5015
|
-
table: target.table,
|
|
5016
|
-
deleted: 0,
|
|
5017
|
-
done: false
|
|
5018
|
-
});
|
|
5019
|
-
continue;
|
|
5020
|
-
}
|
|
5021
|
-
const cutoff = cutoffFor(target.policy, target.anchorType, now);
|
|
5022
|
-
const { deleted, done } = await runBatchedDelete({
|
|
5023
|
-
deleteBatch: (limit) => db.pruneBatch({
|
|
5024
|
-
tableName: target.table,
|
|
5025
|
-
column: target.column,
|
|
5026
|
-
cutoff,
|
|
5027
|
-
limit
|
|
5028
|
-
}),
|
|
5029
|
-
batchSize: target.policy.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
5030
|
-
options
|
|
5031
|
-
});
|
|
5032
|
-
results.push({
|
|
5033
|
-
domain,
|
|
5034
|
-
table: target.table,
|
|
5035
|
-
deleted,
|
|
5036
|
-
done
|
|
5037
|
-
});
|
|
5038
|
-
}
|
|
5039
|
-
return results;
|
|
4934
|
+
const runBatchedDelete = _mastra_core_storage.runRetentionBatches;
|
|
4935
|
+
function runPrune({ db, domain, targets, options }) {
|
|
4936
|
+
return (0, _mastra_core_storage.executeRetentionPrune)({
|
|
4937
|
+
domain,
|
|
4938
|
+
targets,
|
|
4939
|
+
options,
|
|
4940
|
+
cutoffFor: (target, now) => cutoffFor(target.policy, target.anchorType ?? "timestamp", now),
|
|
4941
|
+
deleteBatch: (target, cutoff, limit) => db.pruneBatch({
|
|
4942
|
+
tableName: target.table,
|
|
4943
|
+
column: target.column,
|
|
4944
|
+
cutoff,
|
|
4945
|
+
limit
|
|
4946
|
+
})
|
|
4947
|
+
});
|
|
5040
4948
|
}
|
|
5041
|
-
/**
|
|
5042
|
-
* Resolve a domain's `{ tableKey: policy }` map plus its descriptor into an
|
|
5043
|
-
* ordered list of {@link PruneTarget}s. `order` lists table keys children-first
|
|
5044
|
-
* so cascade-dependent rows are removed before their parents. Table keys not in
|
|
5045
|
-
* `policies` are skipped (unset = keep forever).
|
|
5046
|
-
*/
|
|
5047
4949
|
function resolveTargets({ policies, descriptor, order }) {
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
targets.push({
|
|
5054
|
-
table: entry.table,
|
|
5055
|
-
column: entry.column,
|
|
5056
|
-
anchorType: entry.anchorType ?? "timestamp",
|
|
5057
|
-
policy
|
|
5058
|
-
});
|
|
5059
|
-
}
|
|
5060
|
-
return targets;
|
|
4950
|
+
return (0, _mastra_core_storage.resolveRetentionTargets)({
|
|
4951
|
+
policies,
|
|
4952
|
+
descriptor,
|
|
4953
|
+
order
|
|
4954
|
+
});
|
|
5061
4955
|
}
|
|
5062
4956
|
//#endregion
|
|
5063
4957
|
//#region src/storage/domains/background-tasks/index.ts
|
|
@@ -17315,6 +17209,11 @@ const FEEDBACK_FIELDS = {
|
|
|
17315
17209
|
const TRACE_SELECT = `
|
|
17316
17210
|
r."traceId" AS "traceId",
|
|
17317
17211
|
r."spanId" AS "rootSpanId",
|
|
17212
|
+
r."name" AS "name",
|
|
17213
|
+
r."entityId" AS "entityId",
|
|
17214
|
+
r."parentSpanId" AS "parentSpanId",
|
|
17215
|
+
r."metadata" AS "metadata",
|
|
17216
|
+
r."input" AS "input",
|
|
17318
17217
|
r."threadId" AS "threadId",
|
|
17319
17218
|
r."resourceId" AS "resourceId",
|
|
17320
17219
|
r."startedAt" AS "startedAt",
|
|
@@ -17716,6 +17615,75 @@ LIMIT $${values.length}`,
|
|
|
17716
17615
|
values
|
|
17717
17616
|
};
|
|
17718
17617
|
}
|
|
17618
|
+
function discoveryRegistry(scope) {
|
|
17619
|
+
if (scope === "trace") return TRACE_FIELDS;
|
|
17620
|
+
if (scope === "spans") return SPAN_FIELDS;
|
|
17621
|
+
if (scope === "scores") return SCORE_FIELDS;
|
|
17622
|
+
return FEEDBACK_FIELDS;
|
|
17623
|
+
}
|
|
17624
|
+
function discoverySource(scope) {
|
|
17625
|
+
if (scope === "trace") return "root_scope r";
|
|
17626
|
+
if (scope === "spans") return "current_spans s";
|
|
17627
|
+
if (scope === "scores") return "current_scores s";
|
|
17628
|
+
return "current_feedback s";
|
|
17629
|
+
}
|
|
17630
|
+
function discoveryCollections(scope) {
|
|
17631
|
+
return scope === "trace" ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([scope]);
|
|
17632
|
+
}
|
|
17633
|
+
function compilePostgresTraceQueryObservedFields(schema, plan) {
|
|
17634
|
+
const { ctes, values } = compilePostgresTraceScope(schema, plan, /* @__PURE__ */ new Set());
|
|
17635
|
+
const searchParameter = values.length + 1;
|
|
17636
|
+
const search = plan.search ? `AND strpos(lower('metadata.' || entry.key), lower($${searchParameter})) > 0` : "";
|
|
17637
|
+
if (plan.search) values.push(plan.search);
|
|
17638
|
+
values.push(plan.limit + 1);
|
|
17639
|
+
return {
|
|
17640
|
+
text: `WITH ${ctes.join(",\n")}
|
|
17641
|
+
SELECT 'metadata.' || entry.key AS path, count(*)::bigint AS occurrences
|
|
17642
|
+
FROM root_scope r
|
|
17643
|
+
CROSS JOIN LATERAL jsonb_each(CASE WHEN jsonb_typeof(r."metadataRaw") = 'object' THEN r."metadataRaw" ELSE '{}'::jsonb END) entry
|
|
17644
|
+
WHERE jsonb_typeof(entry.value) = 'string'
|
|
17645
|
+
AND btrim(entry.value #>> '{}') <> ''
|
|
17646
|
+
AND entry.key <> ''
|
|
17647
|
+
AND strpos(entry.key, '.') = 0
|
|
17648
|
+
AND octet_length('metadata.' || entry.key) <= ${_mastra_core_storage.TRACE_QUERY_MAX_PATH_BYTES}
|
|
17649
|
+
AND octet_length(entry.value #>> '{}') <= ${_mastra_core_storage.TRACE_QUERY_MAX_STRING_BYTES}
|
|
17650
|
+
${search}
|
|
17651
|
+
GROUP BY entry.key
|
|
17652
|
+
ORDER BY occurrences DESC, ('metadata.' || entry.key) COLLATE "C" ASC
|
|
17653
|
+
LIMIT $${values.length}`,
|
|
17654
|
+
values
|
|
17655
|
+
};
|
|
17656
|
+
}
|
|
17657
|
+
function compilePostgresTraceQueryValues(schema, plan) {
|
|
17658
|
+
const { ctes, values } = compilePostgresTraceScope(schema, plan, discoveryCollections(plan.predicateScope));
|
|
17659
|
+
let field;
|
|
17660
|
+
if (plan.predicateScope === "trace" && plan.path.startsWith("metadata.")) {
|
|
17661
|
+
const keyParameter = `$${values.length + 1}`;
|
|
17662
|
+
field = `COALESCE(
|
|
17663
|
+
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17664
|
+
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17665
|
+
)`;
|
|
17666
|
+
values.push(plan.path.slice(9));
|
|
17667
|
+
} else field = fieldSql(discoveryRegistry(plan.predicateScope), plan.path);
|
|
17668
|
+
const searchParameter = values.length + 1;
|
|
17669
|
+
const search = plan.search ? `AND strpos(lower(value), lower($${searchParameter})) > 0` : "";
|
|
17670
|
+
if (plan.search) values.push(plan.search);
|
|
17671
|
+
values.push(plan.limit + 1);
|
|
17672
|
+
return {
|
|
17673
|
+
text: `WITH ${ctes.join(",\n")}, extracted AS (
|
|
17674
|
+
SELECT ${field}::text AS value FROM ${discoverySource(plan.predicateScope)}
|
|
17675
|
+
)
|
|
17676
|
+
SELECT value, count(*)::bigint AS count
|
|
17677
|
+
FROM extracted
|
|
17678
|
+
WHERE value IS NOT NULL
|
|
17679
|
+
AND octet_length(value) <= ${_mastra_core_storage.TRACE_QUERY_MAX_STRING_BYTES}
|
|
17680
|
+
${search}
|
|
17681
|
+
GROUP BY value
|
|
17682
|
+
ORDER BY count DESC, value COLLATE "C" ASC
|
|
17683
|
+
LIMIT $${values.length}`,
|
|
17684
|
+
values
|
|
17685
|
+
};
|
|
17686
|
+
}
|
|
17719
17687
|
function asIsoTimestamp$1(value) {
|
|
17720
17688
|
if (value === null || value === void 0) throw new Error("Trace query returned a null timestamp");
|
|
17721
17689
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
@@ -17725,6 +17693,11 @@ function isPostgresStatementTimeout(error) {
|
|
|
17725
17693
|
const candidate = error;
|
|
17726
17694
|
return candidate.code === "57014" && String(candidate.message ?? "").includes("statement timeout");
|
|
17727
17695
|
}
|
|
17696
|
+
function isPostgresResourceLimit(error) {
|
|
17697
|
+
if (!error || typeof error !== "object") return false;
|
|
17698
|
+
const candidate = error;
|
|
17699
|
+
return candidate.code === "53200" || candidate.code === "53400";
|
|
17700
|
+
}
|
|
17728
17701
|
async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
|
|
17729
17702
|
const resolvedTimeoutMs = _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
17730
17703
|
try {
|
|
@@ -17734,9 +17707,33 @@ async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
|
|
|
17734
17707
|
});
|
|
17735
17708
|
} catch (error) {
|
|
17736
17709
|
if (isPostgresStatementTimeout(error)) throw new _mastra_core_storage.TraceQueryExecutionError();
|
|
17710
|
+
if (isPostgresResourceLimit(error)) throw new _mastra_core_storage.TraceQueryResourceLimitError();
|
|
17737
17711
|
throw error;
|
|
17738
17712
|
}
|
|
17739
17713
|
}
|
|
17714
|
+
async function getTraceQueryObservedFields(client, schema, plan, timeoutMs) {
|
|
17715
|
+
if (plan.predicateScope !== "trace") return {
|
|
17716
|
+
observedFields: [],
|
|
17717
|
+
observedFieldsTruncated: false
|
|
17718
|
+
};
|
|
17719
|
+
const query = compilePostgresTraceQueryObservedFields(schema, plan);
|
|
17720
|
+
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17721
|
+
return {
|
|
17722
|
+
observedFields: rows.slice(0, plan.limit).map((row) => _mastra_core_storage.createTraceQueryObservedFieldDescriptor(String(row.path), Number(row.occurrences))),
|
|
17723
|
+
observedFieldsTruncated: rows.length > plan.limit
|
|
17724
|
+
};
|
|
17725
|
+
}
|
|
17726
|
+
async function getTraceQueryValues(client, schema, plan, timeoutMs) {
|
|
17727
|
+
const query = compilePostgresTraceQueryValues(schema, plan);
|
|
17728
|
+
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17729
|
+
return _mastra_core_storage.getTraceQueryValuesResponseSchema.parse({
|
|
17730
|
+
values: rows.slice(0, plan.limit).map((row) => ({
|
|
17731
|
+
value: String(row.value),
|
|
17732
|
+
count: Number(row.count)
|
|
17733
|
+
})),
|
|
17734
|
+
valuesTruncated: rows.length > plan.limit
|
|
17735
|
+
});
|
|
17736
|
+
}
|
|
17740
17737
|
async function queryTraces(client, schema, plan, timeoutMs) {
|
|
17741
17738
|
const query = compilePostgresTraceQuery(schema, plan);
|
|
17742
17739
|
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
@@ -17755,6 +17752,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
|
|
|
17755
17752
|
const traces = visibleRows.map((row) => ({
|
|
17756
17753
|
traceId: String(row.traceId),
|
|
17757
17754
|
rootSpanId: String(row.rootSpanId),
|
|
17755
|
+
name: row.name,
|
|
17756
|
+
entityId: row.entityId ?? null,
|
|
17757
|
+
parentSpanId: row.parentSpanId ?? null,
|
|
17758
|
+
createdAt: asIsoTimestamp$1(row.startedAt),
|
|
17759
|
+
metadata: row.metadata ?? null,
|
|
17760
|
+
inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
|
|
17758
17761
|
threadId: row.threadId == null ? null : String(row.threadId),
|
|
17759
17762
|
resourceId: row.resourceId == null ? null : String(row.resourceId),
|
|
17760
17763
|
startedAt: asIsoTimestamp$1(row.startedAt),
|
|
@@ -18502,7 +18505,7 @@ async function dangerouslyClearTracing(client, schema) {
|
|
|
18502
18505
|
* Use it through `MastraCompositeStore` with a dedicated Postgres connection.
|
|
18503
18506
|
*/
|
|
18504
18507
|
function wrapError(op, error, details) {
|
|
18505
|
-
if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError) throw error;
|
|
18508
|
+
if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
|
|
18506
18509
|
throw new _mastra_core_error.MastraError({
|
|
18507
18510
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", op, "FAILED"),
|
|
18508
18511
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -18683,6 +18686,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18683
18686
|
"metrics",
|
|
18684
18687
|
"logs",
|
|
18685
18688
|
"trace-query",
|
|
18689
|
+
"trace-query-discovery",
|
|
18686
18690
|
"thread-query"
|
|
18687
18691
|
];
|
|
18688
18692
|
return [
|
|
@@ -18690,6 +18694,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18690
18694
|
"logs",
|
|
18691
18695
|
"delta-polling",
|
|
18692
18696
|
"trace-query",
|
|
18697
|
+
"trace-query-discovery",
|
|
18693
18698
|
"thread-query"
|
|
18694
18699
|
];
|
|
18695
18700
|
}
|
|
@@ -18736,6 +18741,12 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18736
18741
|
async queryTraces(plan) {
|
|
18737
18742
|
return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18738
18743
|
}
|
|
18744
|
+
async getTraceQueryObservedFields(plan) {
|
|
18745
|
+
return this.#run("GET_TRACE_QUERY_OBSERVED_FIELDS", () => getTraceQueryObservedFields(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18746
|
+
}
|
|
18747
|
+
async getTraceQueryValues(plan) {
|
|
18748
|
+
return this.#run("GET_TRACE_QUERY_VALUES", () => getTraceQueryValues(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18749
|
+
}
|
|
18739
18750
|
async queryThreads(plan) {
|
|
18740
18751
|
return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18741
18752
|
}
|