@mastra/duckdb 1.3.0-alpha.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # @mastra/duckdb
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - - **Added** `listBranches` and `getSpans` implementations. ([#16154](https://github.com/mastra-ai/mastra/pull/16154))
8
+ - Historical span data is queryable immediately; no migration required.
9
+
10
+ - Added `count_distinct` aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like `threadId` or `resourceId`) stay fast and bounded. ([#16137](https://github.com/mastra-ai/mastra/pull/16137))
11
+
12
+ **New aggregation**
13
+
14
+ `getMetricAggregate`, `getMetricBreakdown`, and `getMetricTimeSeries` accept `aggregation: 'count_distinct'` with a `distinctColumn`. Backends pick the most efficient native implementation — `uniq` on ClickHouse, `approx_count_distinct` on DuckDB.
15
+
16
+ `distinctColumn` is restricted to a low/medium-cardinality categorical allowlist (`entityType`, `entityName`, `parentEntityType`, `parentEntityName`, `rootEntityType`, `rootEntityName`, `name`, `provider`, `model`, `environment`, `executionSource`, `serviceName`). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful.
17
+
18
+ ```ts
19
+ await store.getMetricAggregate({
20
+ name: ['mastra_llm_tokens_total'],
21
+ aggregation: 'count_distinct',
22
+ distinctColumn: 'model',
23
+ filters: { timestamp: { start, end } },
24
+ });
25
+ ```
26
+
27
+ **Server-side TopK**
28
+
29
+ `getMetricBreakdown` accepts `limit` and `orderDirection`, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated `value`; `orderDirection` flips between top-N (`DESC`, default) and bottom-N (`ASC`).
30
+
31
+ ```ts
32
+ await store.getMetricBreakdown({
33
+ name: ['mastra_agent_duration_ms'],
34
+ aggregation: 'sum',
35
+ groupBy: ['threadId'],
36
+ limit: 20,
37
+ orderDirection: 'DESC',
38
+ });
39
+ ```
40
+
41
+ ### Patch Changes
42
+
43
+ - Improved performance of `listTraces` and `listBranches` on DuckDB. The Traces and Branches lists in the observability UI now load noticeably faster, especially on large span tables, because filtering and pagination happen up front and the store only assembles full span data for the rows on the page being viewed. ([#16165](https://github.com/mastra-ai/mastra/pull/16165))
44
+
45
+ No API or behavior changes — return shapes and filter semantics are unchanged, and no migration is required.
46
+
47
+ - Added direct score lookup support to observability storage so score records can be fetched by `scoreId` without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. ([#16162](https://github.com/mastra-ai/mastra/pull/16162))
48
+
49
+ - Updated dependencies [[`6dcd65f`](https://github.com/mastra-ai/mastra/commit/6dcd65f2a34069e6dc43ba35f1d11119b9b40bef), [`86c0298`](https://github.com/mastra-ai/mastra/commit/86c0298e647306423c842f9d5ac827bd616bd13d), [`c05c9a1`](https://github.com/mastra-ai/mastra/commit/c05c9a13230988cef6d438a62f37760f31927bc7), [`ca28c23`](https://github.com/mastra-ai/mastra/commit/ca28c232a2f18801a6cf20fe053479237b4d4fb0), [`e24aacb`](https://github.com/mastra-ai/mastra/commit/e24aacba07bd66f5d95b636dc24016fca26b52cf), [`7679a63`](https://github.com/mastra-ai/mastra/commit/7679a634eae8e8ca459fd87538fdf72b4389b07f), [`7fce309`](https://github.com/mastra-ai/mastra/commit/7fce30912b14170bfc41f0ac736cca0f39fe0cd4), [`1d64a76`](https://github.com/mastra-ai/mastra/commit/1d64a765861a0772ea187bab76e5ed37bf82d042), [`1c2dda8`](https://github.com/mastra-ai/mastra/commit/1c2dda805fbfccc0abf55d4cb20cc34402dc3f0c), [`c721164`](https://github.com/mastra-ai/mastra/commit/c7211643f7ac861f83b19a3757cc921487fc9d75), [`1b55954`](https://github.com/mastra-ai/mastra/commit/1b559541c1e08a10e49d01ffc51a634dfc37a286), [`7997c2e`](https://github.com/mastra-ai/mastra/commit/7997c2e55ddd121562a4098cd8d2b89c68433bf1), [`5adc55e`](https://github.com/mastra-ai/mastra/commit/5adc55e63407be8ee977914957d68bcc2a075ceb), [`7679a63`](https://github.com/mastra-ai/mastra/commit/7679a634eae8e8ca459fd87538fdf72b4389b07f), [`a0d9b6d`](https://github.com/mastra-ai/mastra/commit/a0d9b6d6b810aeaa9e177a0dcc99a4402e609634), [`e97ccb9`](https://github.com/mastra-ai/mastra/commit/e97ccb900f8b7a390ce82c9f8eb8d6eb2c5e3777), [`c5daf48`](https://github.com/mastra-ai/mastra/commit/c5daf48556e98c46ae06caf00f92c249912007e9), [`70017d7`](https://github.com/mastra-ai/mastra/commit/70017d72ab741b5d7040e2a15c251a317782e39e), [`cd96779`](https://github.com/mastra-ai/mastra/commit/cd9677937f113b2856dc8b9f3d4bdabcee58bb2e), [`b0c7022`](https://github.com/mastra-ai/mastra/commit/b0c70224f80dad7c0cdbfb22cbff22e0f75c064f), [`e4942bc`](https://github.com/mastra-ai/mastra/commit/e4942bc7fdc903572f7d84f26d5e15f9d39c763d)]:
50
+ - @mastra/core@1.32.0
51
+
52
+ ## 1.3.0-alpha.1
53
+
54
+ ### Patch Changes
55
+
56
+ - Improved performance of `listTraces` and `listBranches` on DuckDB. The Traces and Branches lists in the observability UI now load noticeably faster, especially on large span tables, because filtering and pagination happen up front and the store only assembles full span data for the rows on the page being viewed. ([#16165](https://github.com/mastra-ai/mastra/pull/16165))
57
+
58
+ No API or behavior changes — return shapes and filter semantics are unchanged, and no migration is required.
59
+
60
+ - Updated dependencies [[`ca28c23`](https://github.com/mastra-ai/mastra/commit/ca28c232a2f18801a6cf20fe053479237b4d4fb0)]:
61
+ - @mastra/core@1.32.0-alpha.3
62
+
3
63
  ## 1.3.0-alpha.0
4
64
 
5
65
  ### Minor Changes
@@ -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.3.0-alpha.0"
6
+ version: "1.3.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.0-alpha.0",
2
+ "version": "1.3.0",
3
3
  "package": "@mastra/duckdb",
4
4
  "exports": {
5
5
  "DuckDBConnection": {
package/dist/index.cjs CHANGED
@@ -583,7 +583,7 @@ var ObservabilityStorageDuckDB = class extends storage.ObservabilityStorage {
583
583
  return null;
584
584
  }
585
585
  if (!this.loadPromise) {
586
- this.loadPromise = import('./observability-YJW7WSXX.cjs').then(({ ObservabilityStorageDuckDB: ObservabilityStorageDuckDB2 }) => {
586
+ this.loadPromise = import('./observability-2PJ44OVC.cjs').then(({ ObservabilityStorageDuckDB: ObservabilityStorageDuckDB2 }) => {
587
587
  const delegate = new ObservabilityStorageDuckDB2({ db: this.db });
588
588
  this.delegate = delegate;
589
589
  return delegate;
package/dist/index.js CHANGED
@@ -582,7 +582,7 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
582
582
  return null;
583
583
  }
584
584
  if (!this.loadPromise) {
585
- this.loadPromise = import('./observability-2R7ZOHEZ.js').then(({ ObservabilityStorageDuckDB: ObservabilityStorageDuckDB2 }) => {
585
+ this.loadPromise = import('./observability-7LL4LLXY.js').then(({ ObservabilityStorageDuckDB: ObservabilityStorageDuckDB2 }) => {
586
586
  const delegate = new ObservabilityStorageDuckDB2({ db: this.db });
587
587
  this.delegate = delegate;
588
588
  return delegate;
@@ -2436,11 +2436,91 @@ function rowToSpanRecord(row) {
2436
2436
  updatedAt: null
2437
2437
  };
2438
2438
  }
2439
- function buildHasChildErrorClause(hasChildError) {
2439
+ function buildHasChildErrorClause(hasChildError, rootAlias) {
2440
2440
  if (hasChildError === void 0) return "";
2441
- const base = `SELECT 1 FROM reconstructed_spans c WHERE c.traceId = root_spans.traceId AND c.spanId != root_spans.spanId AND c.error IS NOT NULL`;
2441
+ const base = `SELECT 1 FROM span_events c WHERE c.traceId = ${rootAlias}.traceId AND c.spanId != ${rootAlias}.spanId AND c.error IS NOT NULL`;
2442
2442
  return hasChildError ? `EXISTS (${base})` : `NOT EXISTS (${base})`;
2443
2443
  }
2444
+ var PREFILTER_KEYS = /* @__PURE__ */ new Set([
2445
+ "traceId",
2446
+ "spanId",
2447
+ "parentSpanId",
2448
+ "name",
2449
+ "spanType",
2450
+ "source",
2451
+ "entityType",
2452
+ "entityId",
2453
+ "entityName",
2454
+ "entityVersionId",
2455
+ "experimentId",
2456
+ "userId",
2457
+ "organizationId",
2458
+ "resourceId",
2459
+ "runId",
2460
+ "sessionId",
2461
+ "threadId",
2462
+ "requestId",
2463
+ "environment",
2464
+ "serviceName"
2465
+ ]);
2466
+ var SAFE_PREFILTER_ORDER_FIELDS = /* @__PURE__ */ new Set(["startedAt"]);
2467
+ function intersectTimestampRange(existing, incoming) {
2468
+ if (!existing) return { ...incoming };
2469
+ const merged = { ...existing };
2470
+ if (incoming.start !== void 0) {
2471
+ if (merged.start === void 0 || incoming.start.getTime() > merged.start.getTime()) {
2472
+ merged.start = incoming.start;
2473
+ merged.startExclusive = incoming.startExclusive;
2474
+ } else if (incoming.start.getTime() === merged.start.getTime()) {
2475
+ merged.startExclusive = (merged.startExclusive ?? false) || (incoming.startExclusive ?? false);
2476
+ }
2477
+ }
2478
+ if (incoming.end !== void 0) {
2479
+ if (merged.end === void 0 || incoming.end.getTime() < merged.end.getTime()) {
2480
+ merged.end = incoming.end;
2481
+ merged.endExclusive = incoming.endExclusive;
2482
+ } else if (incoming.end.getTime() === merged.end.getTime()) {
2483
+ merged.endExclusive = (merged.endExclusive ?? false) || (incoming.endExclusive ?? false);
2484
+ }
2485
+ }
2486
+ return merged;
2487
+ }
2488
+ function partitionAnchorFilters(filters) {
2489
+ const prefilter = {};
2490
+ const postAgg = {};
2491
+ let hasChildError;
2492
+ for (const [key, value] of Object.entries(filters)) {
2493
+ if (value === void 0 || value === null) continue;
2494
+ if (key === "hasChildError") {
2495
+ if (typeof value === "boolean") hasChildError = value;
2496
+ continue;
2497
+ }
2498
+ if (key === "startedAt") {
2499
+ prefilter.timestamp = intersectTimestampRange(
2500
+ prefilter.timestamp,
2501
+ value
2502
+ );
2503
+ continue;
2504
+ }
2505
+ if (key === "endedAt") {
2506
+ postAgg.endedAt = value;
2507
+ const dateRange = value;
2508
+ if (dateRange?.end) {
2509
+ prefilter.timestamp = intersectTimestampRange(prefilter.timestamp, {
2510
+ end: dateRange.end,
2511
+ endExclusive: dateRange.endExclusive
2512
+ });
2513
+ }
2514
+ continue;
2515
+ }
2516
+ if (PREFILTER_KEYS.has(key)) {
2517
+ prefilter[key] = value;
2518
+ continue;
2519
+ }
2520
+ postAgg[key] = value;
2521
+ }
2522
+ return { prefilter, postAgg, hasChildError };
2523
+ }
2444
2524
  function toValuesTuple(row) {
2445
2525
  return [
2446
2526
  v(row.eventType),
@@ -2619,44 +2699,82 @@ async function listTraces(db, args) {
2619
2699
  const page = Number(args.pagination?.page ?? 0);
2620
2700
  const perPage = Number(args.pagination?.perPage ?? 10);
2621
2701
  const orderBy = { field: args.orderBy?.field ?? "startedAt", direction: args.orderBy?.direction ?? "DESC" };
2622
- const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
2623
- const orderByClause = buildOrderByClause(orderBy);
2624
- const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2625
- const filterParts = [];
2626
- if (filterClause) filterParts.push(filterClause.replace(/^WHERE\s+/i, ""));
2627
- const hasChildError = typeof filters.hasChildError === "boolean" ? filters.hasChildError : void 0;
2628
- const childErrorClause = buildHasChildErrorClause(hasChildError);
2629
- if (childErrorClause) filterParts.push(childErrorClause);
2630
- const combinedFilterClause = filterParts.length > 0 ? `WHERE ${filterParts.join(" AND ")}` : "";
2631
- const cteSql = `
2632
- WITH reconstructed_spans AS (
2702
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters);
2703
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
2704
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`];
2705
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
2706
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
2707
+ const outerAlias = "outer_root";
2708
+ const orderDir = orderBy.direction.toUpperCase();
2709
+ if (orderDir !== "ASC" && orderDir !== "DESC") {
2710
+ throw new Error(`Invalid sort direction: ${orderBy.direction}`);
2711
+ }
2712
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
2713
+ const hasPostAggFilters = Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter;
2714
+ if (!hasPostAggFilters) {
2715
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
2716
+ const offset = page * perPage;
2717
+ const countSql2 = `
2718
+ SELECT COUNT(*) as total
2719
+ FROM span_events AS ${outerAlias}
2720
+ ${prefilterWhere}
2721
+ `;
2722
+ const countResult2 = await db.query(countSql2, prefilterParams);
2723
+ const total2 = Number(countResult2[0]?.total ?? 0);
2724
+ const pageSql = `
2725
+ WITH page_roots AS (
2726
+ SELECT traceId, spanId
2727
+ FROM span_events AS ${outerAlias}
2728
+ ${prefilterWhere}
2729
+ ${prefilterOrderBy}
2730
+ LIMIT ? OFFSET ?
2731
+ )
2633
2732
  ${SPAN_RECONSTRUCT_SELECT}
2733
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_roots)
2634
2734
  GROUP BY traceId, spanId
2735
+ ${buildOrderByClause(orderBy)}
2736
+ `;
2737
+ const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]);
2738
+ const spans2 = rows2.map((row) => rowToSpanRecord(row));
2739
+ return {
2740
+ pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
2741
+ spans: storage.toTraceSpans(spans2)
2742
+ };
2743
+ }
2744
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
2745
+ const postAggParts = [];
2746
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
2747
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
2748
+ if (childErrorClause) postAggParts.push(childErrorClause);
2749
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
2750
+ const cteSql = `
2751
+ WITH candidate_roots AS (
2752
+ SELECT traceId, spanId
2753
+ FROM span_events AS ${outerAlias}
2754
+ ${prefilterWhere}
2635
2755
  ),
2636
2756
  root_spans AS (
2637
- SELECT * FROM reconstructed_spans
2638
- WHERE parentSpanId IS NULL
2757
+ ${SPAN_RECONSTRUCT_SELECT}
2758
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
2759
+ GROUP BY traceId, spanId
2639
2760
  )
2640
2761
  `;
2762
+ const orderByClause = buildOrderByClause(orderBy);
2763
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2641
2764
  const countSql = `
2642
2765
  ${cteSql}
2643
- SELECT COUNT(*) as total FROM root_spans ${combinedFilterClause}
2766
+ SELECT COUNT(*) as total FROM root_spans ${postAggWhere}
2644
2767
  `;
2645
- const countResult = await db.query(countSql, filterParams);
2768
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
2646
2769
  const total = Number(countResult[0]?.total ?? 0);
2647
2770
  const dataSql = `
2648
2771
  ${cteSql}
2649
- SELECT * FROM root_spans ${combinedFilterClause} ${orderByClause} ${paginationClause}
2772
+ SELECT * FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause}
2650
2773
  `;
2651
- const rows = await db.query(dataSql, [...filterParams, ...paginationParams]);
2774
+ const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]);
2652
2775
  const spans = rows.map((row) => rowToSpanRecord(row));
2653
2776
  return {
2654
- pagination: {
2655
- total,
2656
- page,
2657
- perPage,
2658
- hasMore: (page + 1) * perPage < total
2659
- },
2777
+ pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2660
2778
  spans: storage.toTraceSpans(spans)
2661
2779
  };
2662
2780
  }
@@ -2682,37 +2800,92 @@ async function listBranches(db, args) {
2682
2800
  const page = Number(args.pagination?.page ?? 0);
2683
2801
  const perPage = Number(args.pagination?.perPage ?? 10);
2684
2802
  const orderBy = { field: args.orderBy?.field ?? "startedAt", direction: args.orderBy?.direction ?? "DESC" };
2685
- const { spanType, ...passthroughFilters } = filters;
2686
- const { clause: filterClause, params: filterParams } = buildWhereClause(passthroughFilters);
2687
- const orderByClause = buildOrderByClause(orderBy);
2688
- const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2689
- let prefilterClause;
2690
- let prefilterParams;
2691
- if (typeof spanType === "string") {
2692
- if (!storage.BRANCH_SPAN_TYPES.includes(spanType)) {
2803
+ const userSpanType = filters.spanType;
2804
+ if (typeof userSpanType === "string" && !storage.BRANCH_SPAN_TYPES.includes(userSpanType)) {
2805
+ return {
2806
+ pagination: { total: 0, page, perPage, hasMore: false },
2807
+ branches: []
2808
+ };
2809
+ }
2810
+ const { spanType: _spanType, ...rest } = filters;
2811
+ const { prefilter, postAgg} = partitionAnchorFilters(rest);
2812
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
2813
+ const prefilterParts = [`eventType = 'start'`];
2814
+ let spanTypeParams;
2815
+ if (typeof userSpanType === "string") {
2816
+ prefilterParts.push(`spanType = ?`);
2817
+ spanTypeParams = [userSpanType];
2818
+ } else {
2819
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
2820
+ spanTypeParams = [...storage.BRANCH_SPAN_TYPES];
2821
+ }
2822
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
2823
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
2824
+ const prefilterParams = [...spanTypeParams, ...prefilterFilterParams];
2825
+ const outerAlias = "outer_anchor";
2826
+ const orderDir = orderBy.direction.toUpperCase();
2827
+ if (orderDir !== "ASC" && orderDir !== "DESC") {
2828
+ throw new Error(`Invalid sort direction: ${orderBy.direction}`);
2829
+ }
2830
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
2831
+ const hasPostAggFilters = Object.keys(postAgg).length > 0 || !canOrderInPrefilter;
2832
+ if (!hasPostAggFilters) {
2833
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
2834
+ const offset = page * perPage;
2835
+ const countSql2 = `
2836
+ SELECT COUNT(*) as total
2837
+ FROM span_events AS ${outerAlias}
2838
+ ${prefilterWhere}
2839
+ `;
2840
+ const countResult2 = await db.query(countSql2, prefilterParams);
2841
+ const total2 = Number(countResult2[0]?.total ?? 0);
2842
+ if (total2 === 0) {
2693
2843
  return {
2694
2844
  pagination: { total: 0, page, perPage, hasMore: false },
2695
2845
  branches: []
2696
2846
  };
2697
2847
  }
2698
- prefilterClause = `WHERE spanType = ?`;
2699
- prefilterParams = [spanType];
2700
- } else {
2701
- prefilterClause = `WHERE spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`;
2702
- prefilterParams = [...storage.BRANCH_SPAN_TYPES];
2848
+ const pageSql = `
2849
+ WITH page_anchors AS (
2850
+ SELECT traceId, spanId
2851
+ FROM span_events AS ${outerAlias}
2852
+ ${prefilterWhere}
2853
+ ${prefilterOrderBy}
2854
+ LIMIT ? OFFSET ?
2855
+ )
2856
+ ${SPAN_RECONSTRUCT_SELECT}
2857
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_anchors)
2858
+ GROUP BY traceId, spanId
2859
+ ${buildOrderByClause(orderBy)}
2860
+ `;
2861
+ const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]);
2862
+ const spans2 = rows2.map((row) => rowToSpanRecord(row));
2863
+ return {
2864
+ pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
2865
+ branches: storage.toTraceSpans(spans2)
2866
+ };
2703
2867
  }
2868
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
2869
+ const postAggWhere = postAggClause ? postAggClause : "";
2870
+ const orderByClause = buildOrderByClause(orderBy);
2871
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2704
2872
  const cteSql = `
2705
- WITH branch_anchors AS (
2873
+ WITH candidate_anchors AS (
2874
+ SELECT traceId, spanId
2875
+ FROM span_events AS ${outerAlias}
2876
+ ${prefilterWhere}
2877
+ ),
2878
+ branch_anchors AS (
2706
2879
  ${SPAN_RECONSTRUCT_SELECT}
2707
- ${prefilterClause}
2880
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
2708
2881
  GROUP BY traceId, spanId
2709
2882
  )
2710
2883
  `;
2711
2884
  const countSql = `
2712
2885
  ${cteSql}
2713
- SELECT COUNT(*) as total FROM branch_anchors ${filterClause}
2886
+ SELECT COUNT(*) as total FROM branch_anchors ${postAggWhere}
2714
2887
  `;
2715
- const countResult = await db.query(countSql, [...prefilterParams, ...filterParams]);
2888
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
2716
2889
  const total = Number(countResult[0]?.total ?? 0);
2717
2890
  if (total === 0) {
2718
2891
  return {
@@ -2722,17 +2895,12 @@ async function listBranches(db, args) {
2722
2895
  }
2723
2896
  const dataSql = `
2724
2897
  ${cteSql}
2725
- SELECT * FROM branch_anchors ${filterClause} ${orderByClause} ${paginationClause}
2898
+ SELECT * FROM branch_anchors ${postAggWhere} ${orderByClause} ${paginationClause}
2726
2899
  `;
2727
- const rows = await db.query(dataSql, [...prefilterParams, ...filterParams, ...paginationParams]);
2900
+ const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]);
2728
2901
  const spans = rows.map((row) => rowToSpanRecord(row));
2729
2902
  return {
2730
- pagination: {
2731
- total,
2732
- page,
2733
- perPage,
2734
- hasMore: (page + 1) * perPage < total
2735
- },
2903
+ pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2736
2904
  branches: storage.toTraceSpans(spans)
2737
2905
  };
2738
2906
  }
@@ -2958,5 +3126,5 @@ var ObservabilityStorageDuckDB = class extends storage.ObservabilityStorage {
2958
3126
  };
2959
3127
 
2960
3128
  exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB;
2961
- //# sourceMappingURL=observability-YJW7WSXX.cjs.map
2962
- //# sourceMappingURL=observability-YJW7WSXX.cjs.map
3129
+ //# sourceMappingURL=observability-2PJ44OVC.cjs.map
3130
+ //# sourceMappingURL=observability-2PJ44OVC.cjs.map