@mastra/duckdb 1.15.0 → 1.16.0-alpha.1

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.
Files changed (31) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/reference-storage-retention.md +1 -1
  4. package/dist/{features-BI3qUHJ7.js → features-B8gl38Ue.js} +5 -6
  5. package/dist/{features-BI3qUHJ7.js.map → features-B8gl38Ue.js.map} +1 -1
  6. package/dist/{features-CFAYHYYE.cjs → features-DH60yLCA.cjs} +11 -7
  7. package/dist/{features-CFAYHYYE.cjs.map → features-DH60yLCA.cjs.map} +1 -1
  8. package/dist/index.cjs +10 -8
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +10 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/{observability-642joQI8.js → observability-Chrw9Di2.js} +192 -86
  13. package/dist/observability-Chrw9Di2.js.map +1 -0
  14. package/dist/{observability-C9FRo6K3.cjs → observability-Dlt85fvH.cjs} +192 -86
  15. package/dist/observability-Dlt85fvH.cjs.map +1 -0
  16. package/dist/storage/domains/observability/ddl.d.ts +1 -1
  17. package/dist/storage/domains/observability/ddl.d.ts.map +1 -1
  18. package/dist/storage/domains/observability/index.d.ts +3 -2
  19. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  20. package/dist/storage/domains/observability/span-query.d.ts +8 -0
  21. package/dist/storage/domains/observability/span-query.d.ts.map +1 -1
  22. package/dist/storage/domains/observability/trace-aggregate.d.ts +7 -1
  23. package/dist/storage/domains/observability/trace-aggregate.d.ts.map +1 -1
  24. package/dist/storage/domains/observability/tracing.d.ts.map +1 -1
  25. package/dist/storage/features.d.ts +1 -0
  26. package/dist/storage/features.d.ts.map +1 -1
  27. package/dist/storage/index.d.ts +1 -0
  28. package/dist/storage/index.d.ts.map +1 -1
  29. package/package.json +8 -8
  30. package/dist/observability-642joQI8.js.map +0 -1
  31. package/dist/observability-C9FRo6K3.cjs.map +0 -1
@@ -1,11 +1,11 @@
1
- import { a as DuckDBConnection, i as timeToFirstChunkSupported, o as DuckDBQueryTimeoutError, r as timeToFirstChunkFeatures, t as spanQueryFeatures } from "./features-BI3qUHJ7.js";
1
+ import { a as timeToFirstChunkSupported, i as timeToFirstChunkFeatures, n as spanQueryFeatures, o as DuckDBConnection, s as DuckDBQueryTimeoutError, t as spanAggregateFeatures } from "./features-B8gl38Ue.js";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
3
  import * as coreStorage from "@mastra/core/storage";
4
4
  import { BRANCH_SPAN_TYPES, METRIC_DISTINCT_COLUMNS, ObservabilityStorage, computeTraceStatus, createStorageErrorId, executeRetentionPrune, feedbackRecordSchema, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, resolveRetentionTargets, retentionCutoffMs, toTraceSpans } from "@mastra/core/storage";
5
5
  import { coreFeatures } from "@mastra/core/features";
6
6
  import { EntityType } from "@mastra/core/observability";
7
7
  import { parseFieldKey } from "@mastra/core/utils";
8
- //#region src/storage/retention.ts
8
+ // src/storage/retention.ts
9
9
  function cutoffFor(policy, now = Date.now()) {
10
10
  return new Date(retentionCutoffMs(policy, now));
11
11
  }
@@ -30,8 +30,7 @@ function resolveTargets({ policies, descriptor, order }) {
30
30
  order
31
31
  });
32
32
  }
33
- //#endregion
34
- //#region src/storage/domains/observability/ddl.ts
33
+ // src/storage/domains/observability/ddl.ts
35
34
  /**
36
35
  * DDL statements for DuckDB observability tables.
37
36
  * All tables use append-only patterns with a single `timestamp` column.
@@ -80,6 +79,12 @@ CREATE TABLE IF NOT EXISTS span_events (
80
79
  entityVersionId VARCHAR,
81
80
  parentEntityVersionId VARCHAR,
82
81
  rootEntityVersionId VARCHAR,
82
+ parentEntityType VARCHAR,
83
+ parentEntityId VARCHAR,
84
+ parentEntityName VARCHAR,
85
+ rootEntityType VARCHAR,
86
+ rootEntityId VARCHAR,
87
+ rootEntityName VARCHAR,
83
88
 
84
89
  -- Context
85
90
  userId VARCHAR,
@@ -372,6 +377,12 @@ const ALL_MIGRATIONS = [
372
377
  `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS hasOutputPayload BOOLEAN`,
373
378
  `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS hasErrorPayload BOOLEAN`,
374
379
  `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS timeToFirstChunkMs DOUBLE`,
380
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`,
381
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`,
382
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`,
383
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`,
384
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`,
385
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`,
375
386
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
376
387
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
377
388
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
@@ -481,8 +492,7 @@ const ALL_MIGRATIONS = [
481
492
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS reviewStatus VARCHAR DEFAULT 'needs-review'`,
482
493
  `ALTER TABLE feedback_events ALTER COLUMN traceId DROP NOT NULL`
483
494
  ];
484
- //#endregion
485
- //#region src/storage/domains/observability/discovery.ts
495
+ // src/storage/domains/observability/discovery.ts
486
496
  function unionDistinctQueries(selects, orderBy) {
487
497
  return `${selects.join("\nUNION\n")}\nORDER BY ${orderBy}`;
488
498
  }
@@ -550,8 +560,7 @@ async function getTags(db, args) {
550
560
  buildSelect("log_events")
551
561
  ], "tag"), params)).map((r) => r.tag) };
552
562
  }
553
- //#endregion
554
- //#region src/storage/domains/observability/filters.ts
563
+ // src/storage/domains/observability/filters.ts
555
564
  function buildJsonPath(key) {
556
565
  try {
557
566
  return `$.${parseFieldKey(key)}`;
@@ -701,8 +710,7 @@ function buildPaginationClause(pagination) {
701
710
  params: [pagination.perPage, offset]
702
711
  };
703
712
  }
704
- //#endregion
705
- //#region src/storage/domains/observability/preview-compat.ts
713
+ // src/storage/domains/observability/preview-compat.ts
706
714
  const generator = coreStorage.buildStoredPreview;
707
715
  const canGeneratePreviews = typeof generator === "function";
708
716
  /** SQL detection only; preview formatting stays in TypeScript. Empty strings are intentional. */
@@ -759,8 +767,7 @@ function buildSpanQueryResponse(...args) {
759
767
  }
760
768
  return response;
761
769
  }
762
- //#endregion
763
- //#region src/storage/domains/observability/helpers.ts
770
+ // src/storage/domains/observability/helpers.ts
764
771
  /** Shorthand for {@link DuckDBConnection.sqlValue}. */
765
772
  const v = DuckDBConnection.sqlValue;
766
773
  /** Serialize a value to JSON then SQL-escape it, or return 'NULL'. */
@@ -852,8 +859,7 @@ function previewFromPayload(value) {
852
859
  function apiPreview(value) {
853
860
  return readPreview(value);
854
861
  }
855
- //#endregion
856
- //#region src/storage/domains/observability/polling.ts
862
+ // src/storage/domains/observability/polling.ts
857
863
  const OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
858
864
  function deltaPollingFeatureEnabled() {
859
865
  return coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
@@ -885,8 +891,7 @@ function extendWhereClause(baseClause, extraConditions) {
885
891
  if (!baseClause) return `WHERE ${conditions.join(" AND ")}`;
886
892
  return `${baseClause} AND ${conditions.join(" AND ")}`;
887
893
  }
888
- //#endregion
889
- //#region src/storage/domains/observability/review-status.ts
894
+ // src/storage/domains/observability/review-status.ts
890
895
  const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
891
896
  function isFeedbackReviewStatus(value) {
892
897
  return FEEDBACK_REVIEW_STATUSES.some((status) => status === value);
@@ -910,8 +915,7 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
910
915
  reviewStatus: args.reviewStatus
911
916
  };
912
917
  }
913
- //#endregion
914
- //#region src/storage/domains/observability/feedback.ts
918
+ // src/storage/domains/observability/feedback.ts
915
919
  const FEEDBACK_UPSERT_CLAUSE = `ON CONFLICT (feedbackId) DO UPDATE SET ${[
916
920
  "timestamp",
917
921
  "cursorId",
@@ -1440,8 +1444,7 @@ async function getFeedbackPercentiles(db, args) {
1440
1444
  }
1441
1445
  return { series };
1442
1446
  }
1443
- //#endregion
1444
- //#region src/storage/domains/observability/logs.ts
1447
+ // src/storage/domains/observability/logs.ts
1445
1448
  const COLUMNS_SQL$1 = [
1446
1449
  "logId",
1447
1450
  "timestamp",
@@ -1624,8 +1627,7 @@ async function getDeltaCursor$2(db, filterClause, filterParams) {
1624
1627
  async function getStreamHeadCursor$2(db) {
1625
1628
  return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`))[0]?.cursorId);
1626
1629
  }
1627
- //#endregion
1628
- //#region src/storage/domains/observability/metrics.ts
1630
+ // src/storage/domains/observability/metrics.ts
1629
1631
  const METRIC_FILTER_FIELD_MAPPINGS = {
1630
1632
  traceIds: "traceId",
1631
1633
  experimentIds: "experimentId"
@@ -2158,8 +2160,7 @@ async function getMetricLabelValues(db, args) {
2158
2160
  if (args.limit) params.push(args.limit);
2159
2161
  return { values: (await db.query(`SELECT DISTINCT json_extract_string(labels, ?) AS val FROM metric_events WHERE ${conditions.join(" AND ")} ORDER BY val ${limitClause}`, [labelPath, ...params])).map((r) => r.val) };
2160
2162
  }
2161
- //#endregion
2162
- //#region src/storage/domains/observability/migration.ts
2163
+ // src/storage/domains/observability/migration.ts
2163
2164
  const CURSOR_ID_TABLES = [
2164
2165
  "span_events",
2165
2166
  "metric_events",
@@ -2308,8 +2309,7 @@ async function migrateSignalTables(db, logger) {
2308
2309
  }
2309
2310
  }
2310
2311
  }
2311
- //#endregion
2312
- //#region src/storage/domains/observability/scores.ts
2312
+ // src/storage/domains/observability/scores.ts
2313
2313
  const SCORE_UPSERT_CLAUSE = `ON CONFLICT (scoreId) DO UPDATE SET ${[
2314
2314
  "timestamp",
2315
2315
  "traceId",
@@ -2820,8 +2820,7 @@ async function getScorePercentiles(db, args) {
2820
2820
  }
2821
2821
  return { series };
2822
2822
  }
2823
- //#endregion
2824
- //#region src/storage/domains/observability/trace-query.ts
2823
+ // src/storage/domains/observability/trace-query.ts
2825
2824
  const TRACE_STATUS_SQL = `CASE WHEN r.error IS NOT NULL THEN 'error' ELSE 'success' END`;
2826
2825
  function durationMsSql(startedAt, endedAt) {
2827
2826
  return `date_diff('millisecond', ${startedAt}, ${endedAt})`;
@@ -3948,23 +3947,15 @@ function compileThreadQueryPredicate(predicate) {
3948
3947
  parameterType: rule.valueKind === "timestamp" ? "timestamp" : "scalar"
3949
3948
  }])));
3950
3949
  }
3951
- //#endregion
3952
- //#region src/storage/domains/observability/span-query.ts
3950
+ // src/storage/domains/observability/span-query.ts
3953
3951
  const identityFields = [
3954
3952
  "organizationId",
3955
3953
  "resourceId",
3956
3954
  "traceId",
3957
3955
  "spanId"
3958
3956
  ];
3959
- const missingFields = /* @__PURE__ */ new Set([
3960
- "parentEntityType",
3961
- "parentEntityId",
3962
- "parentEntityName",
3963
- "rootEntityType",
3964
- "rootEntityId",
3965
- "rootEntityName",
3966
- "executionSource"
3967
- ]);
3957
+ /** Span rows store the execution source as `source`. */
3958
+ const columnSql = (field) => field === "executionSource" ? "e.source AS executionSource" : `e.${field}`;
3968
3959
  const identities = identityFields.join(", ");
3969
3960
  const fields = [
3970
3961
  ...identityFields,
@@ -4028,7 +4019,11 @@ function afterCursor(plan) {
4028
4019
  values
4029
4020
  };
4030
4021
  }
4031
- function compileDuckDBSpanQuery(plan) {
4022
+ /**
4023
+ * The spans `querySpans()` selects, as CTEs ending in `selected`: the current completed copy of
4024
+ * each span that started in the window and matches `where`. Values are in placeholder order.
4025
+ */
4026
+ function compileDuckDBSpanSelection(plan) {
4032
4027
  const values = [plan.timeRange.from, plan.timeRange.to];
4033
4028
  const scope = [];
4034
4029
  if (plan.scope) {
@@ -4043,28 +4038,44 @@ function compileDuckDBSpanQuery(plan) {
4043
4038
  sql: "TRUE",
4044
4039
  values: []
4045
4040
  };
4046
- const after = afterCursor(plan);
4047
- values.push(plan.timeRange.from, plan.timeRange.to, ...predicate.values, ...after.values, plan.limit + 1);
4041
+ values.push(plan.timeRange.from, plan.timeRange.to, ...predicate.values);
4048
4042
  const join = identityFields.map((field) => `e.${field} IS NOT DISTINCT FROM c.${field}`).join(" AND ");
4043
+ const partition = identityFields.map((field) => `e.${field}`).join(", ");
4049
4044
  const legacyModelFields = coreStorage.SPAN_QUERY_FIELD_REGISTRY ? "" : `CASE WHEN json_type(e.attributes, '$.model') = 'VARCHAR' THEN json_extract_string(e.attributes, '$.model') END AS model,
4050
4045
  CASE WHEN json_type(e.attributes, '$.provider') = 'VARCHAR' THEN json_extract_string(e.attributes, '$.provider') END AS provider,`;
4051
4046
  return {
4052
- sql: `WITH candidates AS MATERIALIZED (
4047
+ ctes: [
4048
+ `candidates AS MATERIALIZED (
4053
4049
  SELECT ${identities} FROM span_events
4054
4050
  WHERE eventType = 'start' AND timestamp >= CAST(? AS TIMESTAMP) AND timestamp < CAST(? AS TIMESTAMP)
4055
4051
  ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
4056
4052
  GROUP BY ${identities}
4057
- ), ranked AS (
4058
- SELECT ${fields.map((field) => missingFields.has(field) ? `CAST(NULL AS VARCHAR) AS ${field}` : `e.${field}`).join(", ")},
4059
- min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}) AS startedAt, e.endedAt, e.cursorId,
4053
+ )`,
4054
+ `span_versions AS (
4055
+ SELECT ${fields.map(columnSql).join(", ")},
4056
+ min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${partition}) AS startedAt, e.endedAt, e.cursorId,
4060
4057
  ${legacyModelFields}
4061
- date_diff('millisecond', min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}), e.endedAt) AS durationMs,
4058
+ date_diff('millisecond', min(e.timestamp) FILTER (WHERE e.eventType = 'start') OVER (PARTITION BY ${partition}), e.endedAt) AS durationMs,
4062
4059
  CASE WHEN e.error IS NULL THEN 'success' ELSE 'error' END AS status,
4063
4060
  CASE WHEN e.error IS NULL THEN NULL ELSE 'error' END AS error,
4064
- row_number() OVER (PARTITION BY ${identityFields.map((field) => `e.${field}`).join(", ")}
4061
+ row_number() OVER (PARTITION BY ${partition}
4065
4062
  ORDER BY CASE WHEN e.endedAt IS NULL THEN 1 ELSE 0 END, e.cursorId DESC NULLS LAST, e.endedAt DESC) AS rank
4066
4063
  FROM span_events e INNER JOIN candidates c ON ${join}
4067
- )
4064
+ )`,
4065
+ `selected AS (
4066
+ SELECT * FROM span_versions s
4067
+ WHERE rank = 1 AND endedAt IS NOT NULL AND startedAt >= CAST(? AS TIMESTAMP) AND startedAt < CAST(? AS TIMESTAMP) AND (${predicate.sql})
4068
+ )`
4069
+ ],
4070
+ values
4071
+ };
4072
+ }
4073
+ function compileDuckDBSpanQuery(plan) {
4074
+ const { ctes, values } = compileDuckDBSpanSelection(plan);
4075
+ const after = afterCursor(plan);
4076
+ values.push(...after.values, plan.limit + 1);
4077
+ return {
4078
+ sql: `WITH ${ctes.join(", ")}
4068
4079
  SELECT ${[
4069
4080
  ...fields,
4070
4081
  "startedAt",
@@ -4073,7 +4084,7 @@ function compileDuckDBSpanQuery(plan) {
4073
4084
  "status",
4074
4085
  "cursorId"
4075
4086
  ].map((field) => `s.${field}`).join(", ")}
4076
- FROM ranked s WHERE rank = 1 AND endedAt IS NOT NULL AND startedAt >= CAST(? AS TIMESTAMP) AND startedAt < CAST(? AS TIMESTAMP) AND (${predicate.sql}) AND ${after.sql}
4087
+ FROM selected s WHERE ${after.sql}
4077
4088
  ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
4078
4089
  LIMIT ?`,
4079
4090
  values
@@ -4153,8 +4164,7 @@ async function querySpans(db, plan) {
4153
4164
  }
4154
4165
  return buildSpanQueryResponse(plan, hydrated, payloads, metrics);
4155
4166
  }
4156
- //#endregion
4157
- //#region src/storage/domains/observability/thread-query.ts
4167
+ // src/storage/domains/observability/thread-query.ts
4158
4168
  const contextFields = [
4159
4169
  "resourceId",
4160
4170
  "userId",
@@ -4404,8 +4414,7 @@ async function queryThreadRows(db, plan, strategy = "auto") {
4404
4414
  }) : null }
4405
4415
  };
4406
4416
  }
4407
- //#endregion
4408
- //#region src/storage/domains/observability/trace-aggregate.ts
4417
+ // src/storage/domains/observability/trace-aggregate.ts
4409
4418
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
4410
4419
  const DIMENSION_SQL = {
4411
4420
  entityType: "r.entityType",
@@ -4416,7 +4425,7 @@ const DIMENSION_SQL = {
4416
4425
  environment: "r.environment",
4417
4426
  status: TRACE_STATUS_SQL,
4418
4427
  serviceName: "r.serviceName",
4419
- executionSource: "r.executionSource",
4428
+ executionSource: "r.source",
4420
4429
  threadId: "r.threadId",
4421
4430
  resourceId: "r.resourceId",
4422
4431
  userId: "r.userId",
@@ -4424,6 +4433,28 @@ const DIMENSION_SQL = {
4424
4433
  organizationId: "r.organizationId",
4425
4434
  experimentId: "r.experimentId"
4426
4435
  };
4436
+ /** Span column expressions (alias `r`, a `selected` span row) for each groupable span dimension. */
4437
+ const SPAN_DIMENSION_SQL = Object.fromEntries([
4438
+ "name",
4439
+ "spanType",
4440
+ "entityType",
4441
+ "entityId",
4442
+ "entityName",
4443
+ "parentEntityType",
4444
+ "parentEntityName",
4445
+ "rootEntityType",
4446
+ "rootEntityName",
4447
+ "environment",
4448
+ "status",
4449
+ "serviceName",
4450
+ "executionSource",
4451
+ "threadId",
4452
+ "resourceId",
4453
+ "userId",
4454
+ "sessionId",
4455
+ "organizationId",
4456
+ "experimentId"
4457
+ ].map((field) => [field, `r.${field}`]));
4427
4458
  const PERCENTILES = {
4428
4459
  "duration.p50": .5,
4429
4460
  "duration.p90": .9,
@@ -4440,18 +4471,47 @@ const COMPARISON_SQL = {
4440
4471
  };
4441
4472
  function compileDuckDBTraceAggregate(plan, options = {}) {
4442
4473
  const { ctes, values } = compileDuckDBTraceCandidates(plan, "r.*");
4443
- const dimensionSql = (field) => {
4444
- if (field.startsWith("metadata.")) return {
4445
- sql: TRACE_METADATA_VALUE_SQL,
4446
- values: traceMetadataPathValues(field.slice(9))
4447
- };
4448
- const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
4449
- if (sql === void 0) throw new Error(`Unsupported trusted trace-aggregate field: ${field}`);
4450
- return {
4451
- sql,
4474
+ return compileDuckDBAggregate(plan, options, {
4475
+ ctes,
4476
+ values,
4477
+ from: "candidates",
4478
+ dimensionSql: (field) => {
4479
+ if (field.startsWith("metadata.")) return {
4480
+ sql: TRACE_METADATA_VALUE_SQL,
4481
+ values: traceMetadataPathValues(field.slice(9))
4482
+ };
4483
+ return {
4484
+ sql: lookupDimensionSql(DIMENSION_SQL, field),
4485
+ values: []
4486
+ };
4487
+ },
4488
+ usageKey: ["traceId"]
4489
+ });
4490
+ }
4491
+ /**
4492
+ * Compiles a trusted span aggregate plan over the spans `querySpans()` selects. Usage is
4493
+ * attributed by `(traceId, spanId)`, so only the span that recorded a metric row carries it.
4494
+ */
4495
+ function compileDuckDBSpanAggregate(plan, options = {}) {
4496
+ const { ctes, values } = compileDuckDBSpanSelection(plan);
4497
+ return compileDuckDBAggregate(plan, options, {
4498
+ ctes,
4499
+ values,
4500
+ from: "selected",
4501
+ dimensionSql: (field) => ({
4502
+ sql: lookupDimensionSql(SPAN_DIMENSION_SQL, field),
4452
4503
  values: []
4453
- };
4454
- };
4504
+ }),
4505
+ usageKey: ["traceId", "spanId"]
4506
+ });
4507
+ }
4508
+ function lookupDimensionSql(columns, field) {
4509
+ const sql = Object.hasOwn(columns, field) ? columns[field] : void 0;
4510
+ if (sql === void 0) throw new Error(`Unsupported trusted aggregate field: ${field}`);
4511
+ return sql;
4512
+ }
4513
+ function compileDuckDBAggregate(plan, options, source) {
4514
+ const { ctes, values, dimensionSql } = source;
4455
4515
  const distinctSql = (field) => field === "traceId" ? {
4456
4516
  sql: "r.traceId",
4457
4517
  values: []
@@ -4482,16 +4542,18 @@ function compileDuckDBTraceAggregate(plan, options = {}) {
4482
4542
  const hasCost = measureKinds.has("cost");
4483
4543
  if (!hasCost && !measureKinds.has("tokens")) ctes.push(`facts AS (
4484
4544
  SELECT ${factColumns.join(",\n ")}
4485
- FROM candidates r
4545
+ FROM ${source.from} r
4486
4546
  )`);
4487
4547
  else {
4548
+ const keyColumns = source.usageKey.map((key) => `r.${key}`).join(", ");
4488
4549
  ctes.push(`base_facts AS (
4489
- SELECT r.traceId, ${factColumns.join(",\n ")}
4490
- FROM candidates r
4550
+ SELECT ${keyColumns}, ${factColumns.join(",\n ")}
4551
+ FROM ${source.from} r
4491
4552
  )`);
4492
4553
  ctes.push(...compileUsageCtes(plan, values, {
4493
4554
  hasCost,
4494
- dedupe: !options.metricIdsUnique
4555
+ dedupe: !options.metricIdsUnique,
4556
+ key: source.usageKey
4495
4557
  }));
4496
4558
  const usageColumns = USAGE_METRIC_NAMES.map((_, index) => `CASE WHEN u.traceId IS NOT NULL THEN COALESCE(u.t${index}, 0) END AS t${index}`);
4497
4559
  usageColumns.push(`u.traceId IS NOT NULL AS usageBearing`);
@@ -4500,7 +4562,7 @@ function compileDuckDBTraceAggregate(plan, options = {}) {
4500
4562
  SELECT b.*,
4501
4563
  ${usageColumns.join(",\n ")}
4502
4564
  FROM base_facts b
4503
- LEFT JOIN usage u ON u.traceId = b.traceId
4565
+ LEFT JOIN usage u ON ${source.usageKey.map((key) => `u.${key} = b.${key}`).join(" AND ")}
4504
4566
  )`);
4505
4567
  }
4506
4568
  const measureSql = (name, alias = "") => {
@@ -4622,8 +4684,8 @@ function costColumns(alias) {
4622
4684
  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`];
4623
4685
  }
4624
4686
  /**
4625
- * Builds `usage_bounds`, `usage_rows`, and `usage`: one row per candidate trace that has at least
4626
- * one token metric row, holding per-name token sums (`t<i>`, indexed by
4687
+ * Builds `usage_bounds`, `usage_rows`, and `usage`: one row per population row (trace, or span)
4688
+ * that has at least one token metric row, keyed by the source's usage key, holding per-name token sums (`t<i>`, indexed by
4627
4689
  * `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and, for cost requests, the trace's pricing state. Metric
4628
4690
  * rows are matched by traceId, usage metric name, tenant scope, and `timestamp >= from` only: a
4629
4691
  * trace that starts in the window can emit metrics after `to`, and the semi-join bounds the scan.
@@ -4637,7 +4699,7 @@ function costColumns(alias) {
4637
4699
  *
4638
4700
  * Placeholders are bound in text order: `param` pushes each value as its placeholder is emitted.
4639
4701
  */
4640
- function compileUsageCtes(plan, values, { hasCost, dedupe }) {
4702
+ function compileUsageCtes(plan, values, { hasCost, dedupe, key }) {
4641
4703
  const param = (value) => {
4642
4704
  values.push(value);
4643
4705
  return "?";
@@ -4664,9 +4726,10 @@ function compileUsageCtes(plan, values, { hasCost, dedupe }) {
4664
4726
  CASE WHEN m.name IN (${list(costNames)})
4665
4727
  THEN COALESCE(json_type(m.costMetadata, '$.error'), 'NULL') <> 'NULL'
4666
4728
  ELSE FALSE
4667
- END AS hasError` : "";
4729
+ END AS hasError,
4730
+ COALESCE(json_extract_string(m.costMetadata, '$.error') = 'partial_cost', FALSE) AS partialCost` : "";
4668
4731
  const rows = `usage_rows AS (
4669
- SELECT ${dedupe ? "DISTINCT ON (m.metricId) " : ""}m.traceId, m.name, m.value${costSelect}
4732
+ SELECT ${dedupe ? "DISTINCT ON (m.metricId) " : ""}${key.map((column) => `m.${column}`).join(", ")}, m.name, m.value${costSelect}
4670
4733
  FROM metric_events m
4671
4734
  WHERE m.timestamp >= (SELECT minTs FROM usage_bounds)
4672
4735
  AND m.timestamp <= (SELECT maxTs FROM usage_bounds)
@@ -4675,17 +4738,17 @@ function compileUsageCtes(plan, values, { hasCost, dedupe }) {
4675
4738
  )`;
4676
4739
  const columns = USAGE_METRIC_NAMES.map((name) => `sum(value) FILTER (WHERE name = ${param(name)})`).map((sql, index) => `${sql} AS t${index}`);
4677
4740
  if (hasCost) {
4678
- const priced = "costRow AND estimatedCost IS NOT NULL AND costUnit IS NOT NULL AND NOT hasError";
4741
+ const priced = "costRow AND estimatedCost IS NOT NULL AND costUnit IS NOT NULL AND (NOT hasError OR partialCost)";
4679
4742
  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`);
4680
4743
  }
4681
4744
  return [
4682
4745
  bounds,
4683
4746
  rows,
4684
4747
  `usage AS (
4685
- SELECT traceId,
4748
+ SELECT ${key.join(", ")},
4686
4749
  ${columns.join(",\n ")}
4687
4750
  FROM usage_rows
4688
- GROUP BY traceId
4751
+ GROUP BY ${key.join(", ")}
4689
4752
  )`
4690
4753
  ];
4691
4754
  }
@@ -4693,7 +4756,12 @@ function nullableNumber(value) {
4693
4756
  return value === null || value === void 0 ? null : Number(value);
4694
4757
  }
4695
4758
  async function aggregateTraces(db, plan, options = {}) {
4696
- const compiled = compileDuckDBTraceAggregate(plan, options);
4759
+ return runAggregate(db, plan, compileDuckDBTraceAggregate(plan, options));
4760
+ }
4761
+ async function aggregateSpans(db, plan, options = {}) {
4762
+ return runAggregate(db, plan, compileDuckDBSpanAggregate(plan, options));
4763
+ }
4764
+ async function runAggregate(db, plan, compiled) {
4697
4765
  let result;
4698
4766
  try {
4699
4767
  result = await db.query(compiled.sql, compiled.values);
@@ -4727,8 +4795,7 @@ async function aggregateTraces(db, plan, options = {}) {
4727
4795
  truncated
4728
4796
  };
4729
4797
  }
4730
- //#endregion
4731
- //#region src/storage/domains/observability/tracing.ts
4798
+ // src/storage/domains/observability/tracing.ts
4732
4799
  const COLUMNS_SQL = [
4733
4800
  "eventType",
4734
4801
  "timestamp",
@@ -4748,6 +4815,12 @@ const COLUMNS_SQL = [
4748
4815
  "entityVersionId",
4749
4816
  "parentEntityVersionId",
4750
4817
  "rootEntityVersionId",
4818
+ "parentEntityType",
4819
+ "parentEntityId",
4820
+ "parentEntityName",
4821
+ "rootEntityType",
4822
+ "rootEntityId",
4823
+ "rootEntityName",
4751
4824
  "userId",
4752
4825
  "organizationId",
4753
4826
  "resourceId",
@@ -4826,6 +4899,12 @@ const SPAN_RECONSTRUCT_SELECT = `
4826
4899
  ${argMaxNonNull("entityVersionId")},
4827
4900
  ${argMaxNonNull("parentEntityVersionId")},
4828
4901
  ${argMaxNonNull("rootEntityVersionId")},
4902
+ ${argMaxNonNull("parentEntityType")},
4903
+ ${argMaxNonNull("parentEntityId")},
4904
+ ${argMaxNonNull("parentEntityName")},
4905
+ ${argMaxNonNull("rootEntityType")},
4906
+ ${argMaxNonNull("rootEntityId")},
4907
+ ${argMaxNonNull("rootEntityName")},
4829
4908
  ${argMaxNonNull("userId")},
4830
4909
  ${argMaxNonNull("organizationId")},
4831
4910
  ${argMaxNonNull("resourceId")},
@@ -5056,6 +5135,12 @@ function rowToSpanRecord(row) {
5056
5135
  entityVersionId: row.entityVersionId ?? null,
5057
5136
  parentEntityVersionId: row.parentEntityVersionId ?? null,
5058
5137
  rootEntityVersionId: row.rootEntityVersionId ?? null,
5138
+ parentEntityType: row.parentEntityType ?? null,
5139
+ parentEntityId: row.parentEntityId ?? null,
5140
+ parentEntityName: row.parentEntityName ?? null,
5141
+ rootEntityType: row.rootEntityType ?? null,
5142
+ rootEntityId: row.rootEntityId ?? null,
5143
+ rootEntityName: row.rootEntityName ?? null,
5059
5144
  userId: row.userId ?? null,
5060
5145
  organizationId: row.organizationId ?? null,
5061
5146
  resourceId: row.resourceId ?? null,
@@ -5236,6 +5321,12 @@ function toValuesTuple(row) {
5236
5321
  v(row.entityVersionId),
5237
5322
  v(row.parentEntityVersionId),
5238
5323
  v(row.rootEntityVersionId),
5324
+ v(row.parentEntityType),
5325
+ v(row.parentEntityId),
5326
+ v(row.parentEntityName),
5327
+ v(row.rootEntityType),
5328
+ v(row.rootEntityId),
5329
+ v(row.rootEntityName),
5239
5330
  v(row.userId),
5240
5331
  v(row.organizationId),
5241
5332
  v(row.resourceId),
@@ -5289,6 +5380,12 @@ function createStartSpanRow(s) {
5289
5380
  entityVersionId: s.entityVersionId ?? null,
5290
5381
  parentEntityVersionId: s.parentEntityVersionId ?? null,
5291
5382
  rootEntityVersionId: s.rootEntityVersionId ?? null,
5383
+ parentEntityType: s.parentEntityType ?? null,
5384
+ parentEntityId: s.parentEntityId ?? null,
5385
+ parentEntityName: s.parentEntityName ?? null,
5386
+ rootEntityType: s.rootEntityType ?? null,
5387
+ rootEntityId: s.rootEntityId ?? null,
5388
+ rootEntityName: s.rootEntityName ?? null,
5292
5389
  userId: s.userId ?? null,
5293
5390
  organizationId: s.organizationId ?? null,
5294
5391
  resourceId: s.resourceId ?? null,
@@ -5338,6 +5435,12 @@ function createEndSpanRow(s) {
5338
5435
  entityVersionId: s.entityVersionId ?? null,
5339
5436
  parentEntityVersionId: s.parentEntityVersionId ?? null,
5340
5437
  rootEntityVersionId: s.rootEntityVersionId ?? null,
5438
+ parentEntityType: s.parentEntityType ?? null,
5439
+ parentEntityId: s.parentEntityId ?? null,
5440
+ parentEntityName: s.parentEntityName ?? null,
5441
+ rootEntityType: s.rootEntityType ?? null,
5442
+ rootEntityId: s.rootEntityId ?? null,
5443
+ rootEntityName: s.rootEntityName ?? null,
5341
5444
  userId: s.userId ?? null,
5342
5445
  organizationId: s.organizationId ?? null,
5343
5446
  resourceId: s.resourceId ?? null,
@@ -6046,8 +6149,7 @@ async function getBranchStreamHeadCursor(db, userSpanType) {
6046
6149
  if (userSpanType) return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType = ? AND cursorId IS NOT NULL`, [userSpanType]))[0]?.cursorId);
6047
6150
  return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`, [...BRANCH_SPAN_TYPES]))[0]?.cursorId);
6048
6151
  }
6049
- //#endregion
6050
- //#region src/storage/domains/observability/index.ts
6152
+ // src/storage/domains/observability/index.ts
6051
6153
  function buildSignalMigrationRequiredMessage(args) {
6052
6154
  return `
6053
6155
  ===========================================================================
@@ -6181,6 +6283,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends Observ
6181
6283
  "trace-query",
6182
6284
  "trace-aggregate",
6183
6285
  ...spanQueryFeatures,
6286
+ ...spanAggregateFeatures,
6184
6287
  ...typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
6185
6288
  "trace-query-root-duration",
6186
6289
  "trace-query-discovery",
@@ -6205,6 +6308,7 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends Observ
6205
6308
  "trace-query",
6206
6309
  "trace-aggregate",
6207
6310
  ...spanQueryFeatures,
6311
+ ...spanAggregateFeatures,
6208
6312
  ...typeof coreStorage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
6209
6313
  "trace-query-root-duration",
6210
6314
  "trace-query-discovery",
@@ -6253,6 +6357,9 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends Observ
6253
6357
  async aggregateTraces(plan) {
6254
6358
  return aggregateTraces(this.db, plan, { metricIdsUnique: this.metricIdsUnique });
6255
6359
  }
6360
+ async aggregateSpans(plan) {
6361
+ return aggregateSpans(this.db, plan, { metricIdsUnique: this.metricIdsUnique });
6362
+ }
6256
6363
  async getTraceQueryObservedFields(plan) {
6257
6364
  return getTraceQueryObservedFields(this.db, plan);
6258
6365
  }
@@ -6373,7 +6480,6 @@ var ObservabilityStorageDuckDB = class ObservabilityStorageDuckDB extends Observ
6373
6480
  return getFeedbackPercentiles(this.db, args);
6374
6481
  }
6375
6482
  };
6376
- //#endregion
6377
6483
  export { ObservabilityStorageDuckDB };
6378
6484
 
6379
- //# sourceMappingURL=observability-642joQI8.js.map
6485
+ //# sourceMappingURL=observability-Chrw9Di2.js.map