@mastra/duckdb 1.13.0-alpha.2 → 1.13.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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{observability-D4eFjsKJ.cjs → observability-DBm19qBS.cjs} +139 -19
- package/dist/observability-DBm19qBS.cjs.map +1 -0
- package/dist/{observability-DINJlR_a.js → observability-x50aDnBm.js} +139 -19
- package/dist/observability-x50aDnBm.js.map +1 -0
- package/dist/storage/domains/observability/index.d.ts +2 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/migration.d.ts +1 -0
- package/dist/storage/domains/observability/migration.d.ts.map +1 -1
- package/dist/storage/domains/observability/trace-aggregate.d.ts +9 -2
- package/dist/storage/domains/observability/trace-aggregate.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/observability-D4eFjsKJ.cjs.map +0 -1
- package/dist/observability-DINJlR_a.js.map +0 -1
package/dist/docs/SKILL.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -556,7 +556,7 @@ var ObservabilityStorageDuckDB = class extends _mastra_core_storage.Observabilit
|
|
|
556
556
|
async loadDelegate() {
|
|
557
557
|
if (this.delegate) return this.delegate;
|
|
558
558
|
if (this.unavailableError) return null;
|
|
559
|
-
if (!this.loadPromise) this.loadPromise = Promise.resolve().then(() => require("./observability-
|
|
559
|
+
if (!this.loadPromise) this.loadPromise = Promise.resolve().then(() => require("./observability-DBm19qBS.cjs")).then(({ ObservabilityStorageDuckDB }) => {
|
|
560
560
|
const delegate = new ObservabilityStorageDuckDB({ db: this.db });
|
|
561
561
|
this.delegate = delegate;
|
|
562
562
|
return delegate;
|
package/dist/index.js
CHANGED
|
@@ -555,7 +555,7 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
|
|
|
555
555
|
async loadDelegate() {
|
|
556
556
|
if (this.delegate) return this.delegate;
|
|
557
557
|
if (this.unavailableError) return null;
|
|
558
|
-
if (!this.loadPromise) this.loadPromise = import("./observability-
|
|
558
|
+
if (!this.loadPromise) this.loadPromise = import("./observability-x50aDnBm.js").then(({ ObservabilityStorageDuckDB }) => {
|
|
559
559
|
const delegate = new ObservabilityStorageDuckDB({ db: this.db });
|
|
560
560
|
this.delegate = delegate;
|
|
561
561
|
return delegate;
|
|
@@ -3899,18 +3899,7 @@ const COMPARISON_SQL = {
|
|
|
3899
3899
|
gt: ">",
|
|
3900
3900
|
gte: ">="
|
|
3901
3901
|
};
|
|
3902
|
-
|
|
3903
|
-
* Compiles a trusted aggregate plan. The plan's caps are enforced by the planner and trusted here.
|
|
3904
|
-
*
|
|
3905
|
-
* Every candidate root is first reduced to a `facts` row (dimension values, duration, error flag,
|
|
3906
|
-
* countDistinct inputs, bucket) so grouping, measures, and joins only reference plain columns.
|
|
3907
|
-
* Without `interval`, groups are filtered, ordered, and limited in one grouped select. With
|
|
3908
|
-
* `interval`, a `ranked` CTE ranks groups on whole-window measures, then only the top `limit`
|
|
3909
|
-
* groups are expanded into per-bucket rows.
|
|
3910
|
-
*
|
|
3911
|
-
* Parameters are positional, so `values` is appended in the order placeholders appear in the text.
|
|
3912
|
-
*/
|
|
3913
|
-
function compileDuckDBTraceAggregate(plan) {
|
|
3902
|
+
function compileDuckDBTraceAggregate(plan, options = {}) {
|
|
3914
3903
|
const { ctes, values } = compileDuckDBTraceCandidates(plan, "r.*");
|
|
3915
3904
|
const dimensionSql = (field) => {
|
|
3916
3905
|
if (field.startsWith("metadata.")) return {
|
|
@@ -3950,10 +3939,31 @@ function compileDuckDBTraceAggregate(plan) {
|
|
|
3950
3939
|
if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
|
|
3951
3940
|
factColumns.push(`epoch_ms((epoch_ms(r.startedAt) // ${intervalMs}) * ${intervalMs}) AS bucket`);
|
|
3952
3941
|
}
|
|
3953
|
-
|
|
3942
|
+
const measureKinds = new Set(plan.measures.map((measure) => measureRule(measure.name)?.kind));
|
|
3943
|
+
const hasCost = measureKinds.has("cost");
|
|
3944
|
+
if (!hasCost && !measureKinds.has("tokens")) ctes.push(`facts AS (
|
|
3954
3945
|
SELECT ${factColumns.join(",\n ")}
|
|
3955
3946
|
FROM candidates r
|
|
3956
3947
|
)`);
|
|
3948
|
+
else {
|
|
3949
|
+
ctes.push(`base_facts AS (
|
|
3950
|
+
SELECT r.traceId, ${factColumns.join(",\n ")}
|
|
3951
|
+
FROM candidates r
|
|
3952
|
+
)`);
|
|
3953
|
+
ctes.push(...compileUsageCtes(plan, values, {
|
|
3954
|
+
hasCost,
|
|
3955
|
+
dedupe: !options.metricIdsUnique
|
|
3956
|
+
}));
|
|
3957
|
+
const usageColumns = USAGE_METRIC_NAMES.map((_, index) => `CASE WHEN u.traceId IS NOT NULL THEN COALESCE(u.t${index}, 0) END AS t${index}`);
|
|
3958
|
+
usageColumns.push(`u.traceId IS NOT NULL AS usageBearing`);
|
|
3959
|
+
if (hasCost) usageColumns.push(`CASE WHEN u.priced THEN u.cost END AS traceCost`, `COALESCE(u.priced AND NOT u.pricingFailure, FALSE) AS covered`, `u.unitMin AS unitMin`, `u.unitMax AS unitMax`);
|
|
3960
|
+
ctes.push(`facts AS (
|
|
3961
|
+
SELECT b.*,
|
|
3962
|
+
${usageColumns.join(",\n ")}
|
|
3963
|
+
FROM base_facts b
|
|
3964
|
+
LEFT JOIN usage u ON u.traceId = b.traceId
|
|
3965
|
+
)`);
|
|
3966
|
+
}
|
|
3957
3967
|
const measureSql = (name, alias = "") => {
|
|
3958
3968
|
const column = (name) => `${alias}${name}`;
|
|
3959
3969
|
if (name === "count") return "CAST(count(*) AS DOUBLE)";
|
|
@@ -3964,6 +3974,15 @@ function compileDuckDBTraceAggregate(plan) {
|
|
|
3964
3974
|
if (name === "duration.max") return `max(${column("durationMs")})`;
|
|
3965
3975
|
const percentile = PERCENTILES[name];
|
|
3966
3976
|
if (percentile !== void 0) return `quantile_cont(${column("durationMs")}, ${percentile})`;
|
|
3977
|
+
const rule = measureRule(name);
|
|
3978
|
+
if (rule?.kind === "tokens" && rule.metricNames) {
|
|
3979
|
+
const value = rule.metricNames.map((metricName) => column(`t${usageMetricIndex(metricName)}`)).join(" + ");
|
|
3980
|
+
return `CAST(${rule.statistic === "avg" ? "avg" : "sum"}(${value}) AS DOUBLE)`;
|
|
3981
|
+
}
|
|
3982
|
+
if (rule?.kind === "cost") {
|
|
3983
|
+
const aggregate = rule.statistic === "avg" ? "avg" : "sum";
|
|
3984
|
+
return `CASE WHEN ${mixedUnitsSql(alias)} THEN NULL ELSE CAST(${aggregate}(${column("traceCost")}) AS DOUBLE) END`;
|
|
3985
|
+
}
|
|
3967
3986
|
const measure = plan.measures.find((candidate) => candidate.type === "countDistinct" && candidate.name === name);
|
|
3968
3987
|
const distinctColumn = measure && distinctColumns.get(measure.field);
|
|
3969
3988
|
if (distinctColumn === void 0) throw new Error(`Unsupported trusted trace-aggregate measure: ${name}`);
|
|
@@ -3997,7 +4016,11 @@ function compileDuckDBTraceAggregate(plan) {
|
|
|
3997
4016
|
const orderSql = [`${orderTarget} ${direction} NULLS LAST`, ...dimensionColumns.map((column) => `${column} ASC NULLS LAST`)].join(", ");
|
|
3998
4017
|
const groupSql = dimensionColumns.length > 0 ? `\nGROUP BY ${dimensionColumns.join(", ")}` : "";
|
|
3999
4018
|
if (plan.interval === void 0) {
|
|
4000
|
-
const projected = [
|
|
4019
|
+
const projected = [
|
|
4020
|
+
...dimensionColumns,
|
|
4021
|
+
...plan.measures.map((measure, index) => `${measureSql(measure.name)} AS m${index}`),
|
|
4022
|
+
...hasCost ? costColumns("") : []
|
|
4023
|
+
];
|
|
4001
4024
|
values.push(...havingValues, plan.limit + 1);
|
|
4002
4025
|
return {
|
|
4003
4026
|
sql: `WITH ${ctes.join(",\n ")}
|
|
@@ -4022,6 +4045,7 @@ LIMIT ?`,
|
|
|
4022
4045
|
...dimensionColumns.map((column) => `f.${column}`),
|
|
4023
4046
|
"f.bucket",
|
|
4024
4047
|
...plan.measures.map((measure, index) => `${measureSql(measure.name, "f.")} AS m${index}`),
|
|
4048
|
+
...hasCost ? costColumns("f.") : [],
|
|
4025
4049
|
`(SELECT count(*) FROM ranked) > ? AS truncated`
|
|
4026
4050
|
];
|
|
4027
4051
|
values.push(plan.limit, plan.limit);
|
|
@@ -4040,8 +4064,97 @@ ORDER BY g.rank ASC, f.bucket ASC`,
|
|
|
4040
4064
|
values
|
|
4041
4065
|
};
|
|
4042
4066
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4067
|
+
const USAGE_METRIC_NAMES = _mastra_core_storage.TRACE_AGGREGATE_USAGE_METRIC_NAMES;
|
|
4068
|
+
function measureRule(name) {
|
|
4069
|
+
return _mastra_core_storage.isTraceAggregateCanonicalMeasure(name) ? _mastra_core_storage.TRACE_AGGREGATE_MEASURE_REGISTRY[name] : void 0;
|
|
4070
|
+
}
|
|
4071
|
+
function usageMetricIndex(name) {
|
|
4072
|
+
const index = USAGE_METRIC_NAMES.indexOf(name);
|
|
4073
|
+
if (index === -1) throw new Error(`Unsupported trusted trace-aggregate token metric: ${name}`);
|
|
4074
|
+
return index;
|
|
4075
|
+
}
|
|
4076
|
+
/** More than one distinct priced unit in the group; NULL (treated as false) when nothing is priced. */
|
|
4077
|
+
function mixedUnitsSql(alias) {
|
|
4078
|
+
return `min(${alias}unitMin) <> max(${alias}unitMax)`;
|
|
4079
|
+
}
|
|
4080
|
+
/** Row cost fields, projected only for cost requests. */
|
|
4081
|
+
function costColumns(alias) {
|
|
4082
|
+
const mixed = _mastra_core_storage.TRACE_AGGREGATE_MIXED_COST_UNIT.replaceAll("'", "''");
|
|
4083
|
+
return [`CAST(count_if(${alias}covered) AS DOUBLE) / NULLIF(count_if(${alias}usageBearing), 0) AS costCoverage`, `CASE WHEN ${mixedUnitsSql(alias)} THEN '${mixed}' ELSE min(${alias}unitMin) END AS costUnit`];
|
|
4084
|
+
}
|
|
4085
|
+
/**
|
|
4086
|
+
* Builds `usage_bounds`, `usage_rows`, and `usage`: one row per candidate trace that has at least
|
|
4087
|
+
* one token metric row, holding per-name token sums (`t<i>`, indexed by
|
|
4088
|
+
* `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and, for cost requests, the trace's pricing state. Metric
|
|
4089
|
+
* rows are matched by traceId, usage metric name, tenant scope, and `timestamp >= from` only: a
|
|
4090
|
+
* trace that starts in the window can emit metrics after `to`, and the semi-join bounds the scan.
|
|
4091
|
+
*
|
|
4092
|
+
* DuckDB cannot push the `traceId IN (...)` semi-join into the table scan, so `usage_bounds`
|
|
4093
|
+
* first reads the exact timestamp range of the qualifying rows from narrow columns, and
|
|
4094
|
+
* `usage_rows` decompresses the wide columns only inside that range.
|
|
4095
|
+
*
|
|
4096
|
+
* Without the `metricId` primary key, retried copies are collapsed with `DISTINCT ON`; copies are
|
|
4097
|
+
* identical, so whichever survives is the same.
|
|
4098
|
+
*
|
|
4099
|
+
* Placeholders are bound in text order: `param` pushes each value as its placeholder is emitted.
|
|
4100
|
+
*/
|
|
4101
|
+
function compileUsageCtes(plan, values, { hasCost, dedupe }) {
|
|
4102
|
+
const param = (value) => {
|
|
4103
|
+
values.push(value);
|
|
4104
|
+
return "?";
|
|
4105
|
+
};
|
|
4106
|
+
const list = (items) => items.map(param).join(", ");
|
|
4107
|
+
const scope = (alias) => {
|
|
4108
|
+
if (!plan.scope) return "";
|
|
4109
|
+
let sql = `\n AND ${alias}organizationId = ${param(plan.scope.organizationId)}`;
|
|
4110
|
+
if (plan.scope.resourceId !== void 0) sql += `\n AND ${alias}resourceId = ${param(plan.scope.resourceId)}`;
|
|
4111
|
+
return sql;
|
|
4112
|
+
};
|
|
4113
|
+
const costNames = _mastra_core_storage.TRACE_AGGREGATE_COST_METRIC_NAMES;
|
|
4114
|
+
const bounds = `usage_bounds AS (
|
|
4115
|
+
SELECT min(timestamp) AS minTs, max(timestamp) AS maxTs
|
|
4116
|
+
FROM metric_events
|
|
4117
|
+
WHERE traceId IN (SELECT traceId FROM base_facts)
|
|
4118
|
+
AND name IN (${list(USAGE_METRIC_NAMES)})
|
|
4119
|
+
AND timestamp >= CAST(${param(plan.timeRange.from)} AS TIMESTAMP)${scope("")}
|
|
4120
|
+
)`;
|
|
4121
|
+
const costSelect = hasCost ? `,
|
|
4122
|
+
m.estimatedCost,
|
|
4123
|
+
m.costUnit,
|
|
4124
|
+
m.name IN (${list(costNames)}) AS costRow,
|
|
4125
|
+
CASE WHEN m.name IN (${list(costNames)})
|
|
4126
|
+
THEN COALESCE(json_type(m.costMetadata, '$.error'), 'NULL') <> 'NULL'
|
|
4127
|
+
ELSE FALSE
|
|
4128
|
+
END AS hasError` : "";
|
|
4129
|
+
const rows = `usage_rows AS (
|
|
4130
|
+
SELECT ${dedupe ? "DISTINCT ON (m.metricId) " : ""}m.traceId, m.name, m.value${costSelect}
|
|
4131
|
+
FROM metric_events m
|
|
4132
|
+
WHERE m.timestamp >= (SELECT minTs FROM usage_bounds)
|
|
4133
|
+
AND m.timestamp <= (SELECT maxTs FROM usage_bounds)
|
|
4134
|
+
AND m.traceId IN (SELECT traceId FROM base_facts)
|
|
4135
|
+
AND m.name IN (${list(USAGE_METRIC_NAMES)})${scope("m.")}
|
|
4136
|
+
)`;
|
|
4137
|
+
const columns = USAGE_METRIC_NAMES.map((name) => `sum(value) FILTER (WHERE name = ${param(name)})`).map((sql, index) => `${sql} AS t${index}`);
|
|
4138
|
+
if (hasCost) {
|
|
4139
|
+
const priced = "costRow AND estimatedCost IS NOT NULL AND costUnit IS NOT NULL AND NOT hasError";
|
|
4140
|
+
columns.push(`sum(estimatedCost) FILTER (WHERE ${priced}) AS cost`, `bool_or(${priced}) AS priced`, `bool_or(costRow AND (hasError OR (estimatedCost IS NOT NULL AND costUnit IS NULL))) AS pricingFailure`, `min(costUnit) FILTER (WHERE ${priced}) AS unitMin`, `max(costUnit) FILTER (WHERE ${priced}) AS unitMax`);
|
|
4141
|
+
}
|
|
4142
|
+
return [
|
|
4143
|
+
bounds,
|
|
4144
|
+
rows,
|
|
4145
|
+
`usage AS (
|
|
4146
|
+
SELECT traceId,
|
|
4147
|
+
${columns.join(",\n ")}
|
|
4148
|
+
FROM usage_rows
|
|
4149
|
+
GROUP BY traceId
|
|
4150
|
+
)`
|
|
4151
|
+
];
|
|
4152
|
+
}
|
|
4153
|
+
function nullableNumber(value) {
|
|
4154
|
+
return value === null || value === void 0 ? null : Number(value);
|
|
4155
|
+
}
|
|
4156
|
+
async function aggregateTraces(db, plan, options = {}) {
|
|
4157
|
+
const compiled = compileDuckDBTraceAggregate(plan, options);
|
|
4045
4158
|
let result;
|
|
4046
4159
|
try {
|
|
4047
4160
|
result = await db.query(compiled.sql, compiled.values);
|
|
@@ -4060,7 +4173,11 @@ async function aggregateTraces(db, plan) {
|
|
|
4060
4173
|
}
|
|
4061
4174
|
return {
|
|
4062
4175
|
rows: resultRows.map((row) => {
|
|
4063
|
-
const shaped = { measures: Object.fromEntries(plan.measures.map((measure, index) => [measure.name,
|
|
4176
|
+
const shaped = { measures: Object.fromEntries(plan.measures.map((measure, index) => [measure.name, nullableNumber(row[`m${index}`])])) };
|
|
4177
|
+
if ("costCoverage" in row) shaped.cost = {
|
|
4178
|
+
coverage: nullableNumber(row.costCoverage),
|
|
4179
|
+
unit: row.costUnit ?? null
|
|
4180
|
+
};
|
|
4064
4181
|
if (plan.dimensions.length > 0) shaped.dimensions = Object.fromEntries(plan.dimensions.map((dimension, index) => {
|
|
4065
4182
|
const value = row[`d${index}`];
|
|
4066
4183
|
return [dimension, value == null ? null : String(value)];
|
|
@@ -5331,6 +5448,8 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
|
|
|
5331
5448
|
indexed: false
|
|
5332
5449
|
}
|
|
5333
5450
|
};
|
|
5451
|
+
/** Set by `init()`; until then aggregate queries dedupe metric rows on `metricId`. */
|
|
5452
|
+
metricIdsUnique = false;
|
|
5334
5453
|
db;
|
|
5335
5454
|
constructor(config) {
|
|
5336
5455
|
super();
|
|
@@ -5367,6 +5486,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
|
|
|
5367
5486
|
});
|
|
5368
5487
|
await this.db.executeBatch([...ALL_DDL, ...ALL_MIGRATIONS]);
|
|
5369
5488
|
await dropLegacyCursorIdDefaults(this.db);
|
|
5489
|
+
this.metricIdsUnique = await hasPrimaryKey(this.db, "metric_events");
|
|
5370
5490
|
}
|
|
5371
5491
|
/**
|
|
5372
5492
|
* Manually migrate legacy signal tables to the signal-ID primary-key schema.
|
|
@@ -5480,7 +5600,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
|
|
|
5480
5600
|
return queryTraces(this.db, plan);
|
|
5481
5601
|
}
|
|
5482
5602
|
async aggregateTraces(plan) {
|
|
5483
|
-
return aggregateTraces(this.db, plan);
|
|
5603
|
+
return aggregateTraces(this.db, plan, { metricIdsUnique: this.metricIdsUnique });
|
|
5484
5604
|
}
|
|
5485
5605
|
async getTraceQueryObservedFields(plan) {
|
|
5486
5606
|
return getTraceQueryObservedFields(this.db, plan);
|
|
@@ -5604,4 +5724,4 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends _mastr
|
|
|
5604
5724
|
//#endregion
|
|
5605
5725
|
exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB;
|
|
5606
5726
|
|
|
5607
|
-
//# sourceMappingURL=observability-
|
|
5727
|
+
//# sourceMappingURL=observability-DBm19qBS.cjs.map
|