@mastra/duckdb 1.6.4 → 1.7.0-alpha.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.
@@ -1,5 +1,6 @@
1
1
  import { t as DuckDBConnection } from "./db-Dl0fY488.js";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
+ import * as coreStorage from "@mastra/core/storage";
3
4
  import { BRANCH_SPAN_TYPES, METRIC_DISTINCT_COLUMNS, ObservabilityStorage, buildInputPreview, computeTraceStatus, createStorageErrorId, feedbackRecordSchema, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, toTraceSpans } from "@mastra/core/storage";
4
5
  import { coreFeatures } from "@mastra/core/features";
5
6
  import { EntityType } from "@mastra/core/observability";
@@ -2233,7 +2234,7 @@ function getComparisonDateRange(comparePeriod, timestamp) {
2233
2234
  async function createScore(db, args) {
2234
2235
  const s = args.score;
2235
2236
  const scoreSource = s.scoreSource ?? s.source ?? null;
2236
- await db.execute(`INSERT INTO score_events (
2237
+ await db.execute(`INSERT OR REPLACE INTO score_events (
2237
2238
  scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2238
2239
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2239
2240
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
@@ -2277,8 +2278,7 @@ async function createScore(db, args) {
2277
2278
  jsonV(s.tags ?? null),
2278
2279
  jsonV(s.metadata),
2279
2280
  jsonV(s.scope ?? null)
2280
- ].join(", ")})
2281
- ON CONFLICT DO NOTHING`);
2281
+ ].join(", ")})`);
2282
2282
  }
2283
2283
  /** Insert multiple score events in a single statement. */
2284
2284
  async function batchCreateScores(db, args) {
@@ -2326,14 +2326,13 @@ async function batchCreateScores(db, args) {
2326
2326
  jsonV(legacyScore.scope ?? null)
2327
2327
  ].join(", ")})`;
2328
2328
  });
2329
- await db.execute(`INSERT INTO score_events (
2329
+ await db.execute(`INSERT OR REPLACE INTO score_events (
2330
2330
  scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2331
2331
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2332
2332
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
2333
2333
  scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope
2334
2334
  )
2335
- VALUES ${tuples.join(",\n ")}
2336
- ON CONFLICT DO NOTHING`);
2335
+ VALUES ${tuples.join(",\n ")}`);
2337
2336
  }
2338
2337
  /** Query score events with filtering, ordering, and pagination. */
2339
2338
  async function listScores(db, args) {
@@ -2519,6 +2518,323 @@ async function getScorePercentiles(db, args) {
2519
2518
  return { series };
2520
2519
  }
2521
2520
  //#endregion
2521
+ //#region src/storage/domains/observability/trace-query.ts
2522
+ const TRACE_STATUS_SQL = `CASE WHEN r.error IS NOT NULL THEN 'error' ELSE 'success' END`;
2523
+ const TRACE_FIELDS = {
2524
+ traceId: {
2525
+ sql: "r.traceId",
2526
+ parameterType: "scalar"
2527
+ },
2528
+ threadId: {
2529
+ sql: "r.threadId",
2530
+ parameterType: "scalar"
2531
+ },
2532
+ resourceId: {
2533
+ sql: "r.resourceId",
2534
+ parameterType: "scalar"
2535
+ },
2536
+ startedAt: {
2537
+ sql: "r.startedAt",
2538
+ parameterType: "timestamp"
2539
+ },
2540
+ endedAt: {
2541
+ sql: "r.endedAt",
2542
+ parameterType: "timestamp"
2543
+ },
2544
+ entityName: {
2545
+ sql: "r.entityName",
2546
+ parameterType: "scalar"
2547
+ },
2548
+ entityType: {
2549
+ sql: "r.entityType",
2550
+ parameterType: "scalar"
2551
+ },
2552
+ environment: {
2553
+ sql: "r.environment",
2554
+ parameterType: "scalar"
2555
+ },
2556
+ status: {
2557
+ sql: TRACE_STATUS_SQL,
2558
+ parameterType: "scalar"
2559
+ }
2560
+ };
2561
+ const SPAN_FIELDS = {
2562
+ spanType: {
2563
+ sql: "s.spanType",
2564
+ parameterType: "scalar"
2565
+ },
2566
+ error: {
2567
+ sql: "s.error",
2568
+ parameterType: "scalar"
2569
+ }
2570
+ };
2571
+ const SCORE_FIELDS = {
2572
+ scorerId: {
2573
+ sql: "s.scorerId",
2574
+ parameterType: "scalar"
2575
+ },
2576
+ score: {
2577
+ sql: "s.score",
2578
+ parameterType: "scalar"
2579
+ }
2580
+ };
2581
+ const TRACE_SELECT = `
2582
+ r.traceId AS traceId,
2583
+ r.spanId AS rootSpanId,
2584
+ r.threadId AS threadId,
2585
+ r.resourceId AS resourceId,
2586
+ r.startedAt AS startedAt,
2587
+ r.endedAt AS endedAt,
2588
+ r.entityName AS entityName,
2589
+ r.entityType AS entityType,
2590
+ r.environment AS environment,
2591
+ ${TRACE_STATUS_SQL} AS status`;
2592
+ function fieldDefinition(registry, field) {
2593
+ const definition = registry[field];
2594
+ if (definition === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
2595
+ return definition;
2596
+ }
2597
+ function parameterSql(type) {
2598
+ return type === "timestamp" ? "CAST(? AS TIMESTAMP)" : "?";
2599
+ }
2600
+ function compileScalarPredicate(predicate, registry) {
2601
+ if (predicate.type === "boolean") {
2602
+ const values = [];
2603
+ return {
2604
+ sql: predicate.args.map((arg) => {
2605
+ const compiled = compileScalarPredicate(arg, registry);
2606
+ values.push(...compiled.values);
2607
+ return `(${compiled.sql})`;
2608
+ }).join(predicate.operator === "and" ? " AND " : " OR "),
2609
+ values
2610
+ };
2611
+ }
2612
+ if (predicate.type === "not") {
2613
+ const compiled = compileScalarPredicate(predicate.arg, registry);
2614
+ return {
2615
+ sql: `NOT (${compiled.sql})`,
2616
+ values: compiled.values
2617
+ };
2618
+ }
2619
+ const field = fieldDefinition(registry, predicate.field);
2620
+ if (predicate.type === "presence") return {
2621
+ sql: `${field.sql} IS ${predicate.operator === "exists" ? "NOT " : ""}NULL`,
2622
+ values: []
2623
+ };
2624
+ if (predicate.type === "membership") {
2625
+ const list = predicate.values.map(() => parameterSql(field.parameterType)).join(", ");
2626
+ if (predicate.operator === "in") return {
2627
+ sql: `${field.sql} IS NOT NULL AND ${field.sql} IN (${list})`,
2628
+ values: predicate.values
2629
+ };
2630
+ return {
2631
+ sql: `${field.sql} IS NULL OR ${field.sql} NOT IN (${list})`,
2632
+ values: predicate.values
2633
+ };
2634
+ }
2635
+ const parameter = parameterSql(field.parameterType);
2636
+ const operators = {
2637
+ lt: "<",
2638
+ lte: "<=",
2639
+ gt: ">",
2640
+ gte: ">="
2641
+ };
2642
+ if (predicate.operator === "eq") return {
2643
+ sql: `${field.sql} IS NOT DISTINCT FROM ${parameter}`,
2644
+ values: [predicate.value]
2645
+ };
2646
+ if (predicate.operator === "ne") return {
2647
+ sql: `${field.sql} IS DISTINCT FROM ${parameter}`,
2648
+ values: [predicate.value]
2649
+ };
2650
+ const operator = operators[predicate.operator];
2651
+ if (operator === void 0) throw new Error(`Unsupported trusted trace-query operator: ${predicate.operator}`);
2652
+ return {
2653
+ sql: `${field.sql} IS NOT NULL AND ${field.sql} ${operator} ${parameter}`,
2654
+ values: [predicate.value]
2655
+ };
2656
+ }
2657
+ function compilePredicate(predicate) {
2658
+ if (predicate.type === "relation") {
2659
+ const registry = predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS;
2660
+ const compiled = compileScalarPredicate(predicate.predicate, registry);
2661
+ const existence = `EXISTS (
2662
+ SELECT 1 FROM ${predicate.collection === "spans" ? "current_spans" : "current_scores"} s
2663
+ WHERE s.traceId IS NOT NULL
2664
+ AND s.traceId = r.traceId
2665
+ AND (${compiled.sql})
2666
+ )`;
2667
+ return {
2668
+ sql: predicate.quantifier === "some" ? existence : `NOT ${existence}`,
2669
+ values: compiled.values
2670
+ };
2671
+ }
2672
+ if (predicate.type === "boolean") {
2673
+ const values = [];
2674
+ return {
2675
+ sql: predicate.args.map((arg) => {
2676
+ const compiled = compilePredicate(arg);
2677
+ values.push(...compiled.values);
2678
+ return `(${compiled.sql})`;
2679
+ }).join(predicate.operator === "and" ? " AND " : " OR "),
2680
+ values
2681
+ };
2682
+ }
2683
+ if (predicate.type === "not") {
2684
+ const compiled = compilePredicate(predicate.arg);
2685
+ return {
2686
+ sql: `NOT (${compiled.sql})`,
2687
+ values: compiled.values
2688
+ };
2689
+ }
2690
+ return compileScalarPredicate(predicate, TRACE_FIELDS);
2691
+ }
2692
+ function collectRelatedCollections(predicate, collections = /* @__PURE__ */ new Set()) {
2693
+ if (!predicate) return collections;
2694
+ if (predicate.type === "relation") collections.add(predicate.collection);
2695
+ else if (predicate.type === "boolean") for (const arg of predicate.args) collectRelatedCollections(arg, collections);
2696
+ else if (predicate.type === "not") collectRelatedCollections(predicate.arg, collections);
2697
+ return collections;
2698
+ }
2699
+ function compileDuckDBTraceQuery(plan) {
2700
+ const values = [plan.timeRange.from, plan.timeRange.to];
2701
+ const conditions = [
2702
+ `r.endedAt IS NOT NULL`,
2703
+ `r.startedAt >= CAST(? AS TIMESTAMP)`,
2704
+ `r.startedAt < CAST(? AS TIMESTAMP)`
2705
+ ];
2706
+ if (plan.where) {
2707
+ const predicate = compilePredicate(plan.where);
2708
+ conditions.push(`(${predicate.sql})`);
2709
+ values.push(...predicate.values);
2710
+ }
2711
+ const relatedCollections = collectRelatedCollections(plan.where);
2712
+ const ctes = [
2713
+ `root_events AS (
2714
+ SELECT
2715
+ *,
2716
+ CASE
2717
+ WHEN eventType = 'start' THEN timestamp
2718
+ ELSE lag(timestamp) OVER (PARTITION BY traceId, spanId ORDER BY cursorId)
2719
+ END AS startedAt
2720
+ FROM span_events
2721
+ WHERE parentSpanId IS NULL
2722
+ )`,
2723
+ `current_roots AS (
2724
+ SELECT * EXCLUDE (rootRank)
2725
+ FROM (
2726
+ SELECT *, row_number() OVER (PARTITION BY traceId ORDER BY cursorId DESC) AS rootRank
2727
+ FROM root_events
2728
+ )
2729
+ WHERE rootRank = 1
2730
+ )`,
2731
+ `root_scope AS (
2732
+ SELECT *
2733
+ FROM current_roots r
2734
+ WHERE r.endedAt IS NOT NULL
2735
+ AND r.startedAt >= CAST(? AS TIMESTAMP)
2736
+ AND r.startedAt < CAST(? AS TIMESTAMP)
2737
+ )`
2738
+ ];
2739
+ if (relatedCollections.has("spans")) ctes.push(`current_spans AS (
2740
+ SELECT * EXCLUDE (currentRank)
2741
+ FROM (
2742
+ SELECT
2743
+ e.*,
2744
+ row_number() OVER (
2745
+ PARTITION BY e.traceId, e.spanId
2746
+ ORDER BY CASE WHEN e.endedAt IS NULL THEN 1 ELSE 0 END ASC, e.cursorId DESC
2747
+ ) AS currentRank
2748
+ FROM span_events e
2749
+ INNER JOIN root_scope roots ON roots.traceId = e.traceId
2750
+ )
2751
+ WHERE currentRank = 1
2752
+ )`);
2753
+ if (relatedCollections.has("scores")) ctes.push(`current_scores AS (
2754
+ SELECT s.*
2755
+ FROM score_events s
2756
+ INNER JOIN root_scope roots ON roots.traceId = s.traceId
2757
+ )`);
2758
+ ctes.push(`candidates AS (
2759
+ SELECT ${TRACE_SELECT}
2760
+ FROM root_scope r
2761
+ WHERE ${conditions.slice(3).join("\n AND ") || "TRUE"}
2762
+ )`);
2763
+ const candidates = `WITH ${ctes.join(",\n ")}`;
2764
+ if (plan.result === "groups") {
2765
+ const pageCondition = plan.cursor ? `AND threadId > ?` : "";
2766
+ if (plan.cursor) values.push(plan.cursor.threadId);
2767
+ values.push(plan.limit + 1);
2768
+ return {
2769
+ sql: `${candidates}
2770
+ SELECT threadId
2771
+ FROM candidates
2772
+ WHERE threadId IS NOT NULL ${pageCondition}
2773
+ GROUP BY threadId
2774
+ ORDER BY threadId ASC
2775
+ LIMIT ?`,
2776
+ values
2777
+ };
2778
+ }
2779
+ const orderField = plan.orderBy.field;
2780
+ const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
2781
+ let pageCondition = "";
2782
+ if (plan.cursor) {
2783
+ pageCondition = `WHERE (${orderField} ${plan.orderBy.direction === "asc" ? ">" : "<"} CAST(? AS TIMESTAMP) OR (${orderField} = CAST(? AS TIMESTAMP) AND traceId > ?))`;
2784
+ values.push(plan.cursor.sortValue, plan.cursor.sortValue, plan.cursor.traceId);
2785
+ }
2786
+ values.push(plan.limit + 1);
2787
+ return {
2788
+ sql: `${candidates}
2789
+ SELECT *
2790
+ FROM candidates
2791
+ ${pageCondition}
2792
+ ORDER BY ${orderField} ${direction}, traceId ASC
2793
+ LIMIT ?`,
2794
+ values
2795
+ };
2796
+ }
2797
+ function asIsoTimestamp(value) {
2798
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
2799
+ }
2800
+ async function queryTraces(db, plan) {
2801
+ const query = compileDuckDBTraceQuery(plan);
2802
+ const rows = await db.query(query.sql, query.values);
2803
+ const visibleRows = rows.slice(0, plan.limit);
2804
+ if (plan.result === "groups") {
2805
+ const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
2806
+ const last = groups.at(-1);
2807
+ return coreStorage.traceQueryResponseSchema.parse({
2808
+ groups,
2809
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
2810
+ result: "groups",
2811
+ threadId: last.threadId
2812
+ }) : null }
2813
+ });
2814
+ }
2815
+ const traces = visibleRows.map((row) => ({
2816
+ traceId: String(row.traceId),
2817
+ rootSpanId: String(row.rootSpanId),
2818
+ threadId: row.threadId == null ? null : String(row.threadId),
2819
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
2820
+ startedAt: asIsoTimestamp(row.startedAt),
2821
+ endedAt: asIsoTimestamp(row.endedAt),
2822
+ entityName: row.entityName == null ? null : String(row.entityName),
2823
+ entityType: row.entityType == null ? null : String(row.entityType),
2824
+ environment: row.environment == null ? null : String(row.environment),
2825
+ status: row.status
2826
+ }));
2827
+ const last = traces.at(-1);
2828
+ return coreStorage.traceQueryResponseSchema.parse({
2829
+ traces,
2830
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
2831
+ result: "traces",
2832
+ sortValue: last[plan.orderBy.field],
2833
+ traceId: last.traceId
2834
+ }) : null }
2835
+ });
2836
+ }
2837
+ //#endregion
2522
2838
  //#region src/storage/domains/observability/tracing.ts
2523
2839
  const COLUMNS_SQL = [
2524
2840
  "eventType",
@@ -3717,11 +4033,16 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3717
4033
  };
3718
4034
  }
3719
4035
  getFeatures() {
3720
- if (!deltaPollingFeatureEnabled()) return ["metrics", "logs"];
4036
+ if (!deltaPollingFeatureEnabled()) return [
4037
+ "metrics",
4038
+ "logs",
4039
+ "trace-query"
4040
+ ];
3721
4041
  return [
3722
4042
  "metrics",
3723
4043
  "logs",
3724
- "delta-polling"
4044
+ "delta-polling",
4045
+ "trace-query"
3725
4046
  ];
3726
4047
  }
3727
4048
  async createSpan(args) {
@@ -3751,6 +4072,9 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3751
4072
  async listTraces(args) {
3752
4073
  return listTraces(this.db, args);
3753
4074
  }
4075
+ async queryTraces(plan) {
4076
+ return queryTraces(this.db, plan);
4077
+ }
3754
4078
  async listTracesLight(args) {
3755
4079
  if (args.mode === "delta") return super.listTracesLight(args);
3756
4080
  return listTracesLight(this.db, args);
@@ -3858,4 +4182,4 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3858
4182
  //#endregion
3859
4183
  export { ObservabilityStorageDuckDB };
3860
4184
 
3861
- //# sourceMappingURL=observability-CT5eidI9.js.map
4185
+ //# sourceMappingURL=observability-Cqe4mNHq.js.map