@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.
@@ -3,7 +3,7 @@ name: mastra-duckdb
3
3
  description: Documentation for @mastra/duckdb. Use when working with @mastra/duckdb APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/duckdb"
6
- version: "1.13.0-alpha.2"
6
+ version: "1.13.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.13.0-alpha.2",
2
+ "version": "1.13.0",
3
3
  "package": "@mastra/duckdb",
4
4
  "exports": {},
5
5
  "modules": {}
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-D4eFjsKJ.cjs")).then(({ ObservabilityStorageDuckDB }) => {
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-DINJlR_a.js").then(({ ObservabilityStorageDuckDB }) => {
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
- ctes.push(`facts AS (
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 = [...dimensionColumns, ...plan.measures.map((measure, index) => `${measureSql(measure.name)} AS m${index}`)];
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
- async function aggregateTraces(db, plan) {
4044
- const compiled = compileDuckDBTraceAggregate(plan);
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, Number(row[`m${index}`])])) };
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-D4eFjsKJ.cjs.map
5727
+ //# sourceMappingURL=observability-DBm19qBS.cjs.map