@mastra/duckdb 1.3.2 → 1.4.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,15 +1,32 @@
1
1
  import { DuckDBConnection } from './chunk-4LIZE4MC.js';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { BRANCH_SPAN_TYPES, ObservabilityStorage, createStorageErrorId, toTraceSpans, METRIC_DISTINCT_COLUMNS } from '@mastra/core/storage';
3
+ import { BRANCH_SPAN_TYPES, ObservabilityStorage, createStorageErrorId, listTracesArgsSchema, toTraceSpans, listBranchesArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listFeedbackArgsSchema, METRIC_DISTINCT_COLUMNS } from '@mastra/core/storage';
4
4
  import { EntityType } from '@mastra/core/observability';
5
5
  import { parseFieldKey } from '@mastra/core/utils';
6
+ import { coreFeatures } from '@mastra/core/features';
6
7
 
7
8
  // src/storage/domains/observability/ddl.ts
9
+ var SPAN_EVENTS_CURSOR_SEQUENCE_DDL = `
10
+ CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1
11
+ `;
12
+ var METRIC_EVENTS_CURSOR_SEQUENCE_DDL = `
13
+ CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1
14
+ `;
15
+ var LOG_EVENTS_CURSOR_SEQUENCE_DDL = `
16
+ CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1
17
+ `;
18
+ var SCORE_EVENTS_CURSOR_SEQUENCE_DDL = `
19
+ CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1
20
+ `;
21
+ var FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL = `
22
+ CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1
23
+ `;
8
24
  var SPAN_EVENTS_DDL = `
9
25
  CREATE TABLE IF NOT EXISTS span_events (
10
26
  -- Event metadata
11
27
  eventType VARCHAR NOT NULL,
12
28
  timestamp TIMESTAMP NOT NULL,
29
+ cursorId BIGINT DEFAULT nextval('span_events_cursor_id_seq'),
13
30
 
14
31
  -- IDs
15
32
  traceId VARCHAR NOT NULL,
@@ -56,6 +73,7 @@ var METRIC_EVENTS_DDL = `
56
73
  CREATE TABLE IF NOT EXISTS metric_events (
57
74
  -- Event metadata
58
75
  timestamp TIMESTAMP NOT NULL,
76
+ cursorId BIGINT DEFAULT nextval('metric_events_cursor_id_seq'),
59
77
 
60
78
  -- IDs
61
79
  metricId VARCHAR NOT NULL PRIMARY KEY,
@@ -108,6 +126,7 @@ var LOG_EVENTS_DDL = `
108
126
  CREATE TABLE IF NOT EXISTS log_events (
109
127
  -- Event metadata
110
128
  timestamp TIMESTAMP NOT NULL,
129
+ cursorId BIGINT DEFAULT nextval('log_events_cursor_id_seq'),
111
130
 
112
131
  -- IDs
113
132
  logId VARCHAR NOT NULL PRIMARY KEY,
@@ -155,6 +174,7 @@ var SCORE_EVENTS_DDL = `
155
174
  CREATE TABLE IF NOT EXISTS score_events (
156
175
  -- Event metadata
157
176
  timestamp TIMESTAMP NOT NULL,
177
+ cursorId BIGINT DEFAULT nextval('score_events_cursor_id_seq'),
158
178
 
159
179
  -- IDs
160
180
  scoreId VARCHAR NOT NULL PRIMARY KEY,
@@ -206,6 +226,7 @@ var FEEDBACK_EVENTS_DDL = `
206
226
  CREATE TABLE IF NOT EXISTS feedback_events (
207
227
  -- Event metadata
208
228
  timestamp TIMESTAMP NOT NULL,
229
+ cursorId BIGINT DEFAULT nextval('feedback_events_cursor_id_seq'),
209
230
 
210
231
  -- IDs
211
232
  feedbackId VARCHAR NOT NULL PRIMARY KEY,
@@ -254,11 +275,32 @@ CREATE TABLE IF NOT EXISTS feedback_events (
254
275
  metadata JSON,
255
276
  scope JSON
256
277
  )`;
257
- var ALL_DDL = [SPAN_EVENTS_DDL, METRIC_EVENTS_DDL, LOG_EVENTS_DDL, SCORE_EVENTS_DDL, FEEDBACK_EVENTS_DDL];
278
+ var ALL_DDL = [
279
+ SPAN_EVENTS_CURSOR_SEQUENCE_DDL,
280
+ METRIC_EVENTS_CURSOR_SEQUENCE_DDL,
281
+ LOG_EVENTS_CURSOR_SEQUENCE_DDL,
282
+ SCORE_EVENTS_CURSOR_SEQUENCE_DDL,
283
+ FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL,
284
+ SPAN_EVENTS_DDL,
285
+ METRIC_EVENTS_DDL,
286
+ LOG_EVENTS_DDL,
287
+ SCORE_EVENTS_DDL,
288
+ FEEDBACK_EVENTS_DDL
289
+ ];
258
290
  var ALL_MIGRATIONS = [
259
- // Span events
291
+ `CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1`,
292
+ `CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1`,
293
+ `CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1`,
294
+ `CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1`,
295
+ `CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1`,
296
+ // Span events. Existing rows intentionally keep NULL cursorId values; delta
297
+ // polling only applies to rows written after this migration path is in place.
298
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
299
+ `ALTER TABLE span_events ALTER COLUMN cursorId SET DEFAULT nextval('span_events_cursor_id_seq')`,
260
300
  `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
261
- // Metrics
301
+ // Metrics. Legacy rows remain page-visible but are not part of delta polling.
302
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
303
+ `ALTER TABLE metric_events ALTER COLUMN cursorId SET DEFAULT nextval('metric_events_cursor_id_seq')`,
262
304
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
263
305
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
264
306
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -282,7 +324,9 @@ var ALL_MIGRATIONS = [
282
324
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS costMetadata JSON`,
283
325
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS metadata JSON`,
284
326
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS scope JSON`,
285
- // Logs
327
+ // Logs. Legacy rows remain page-visible but are not part of delta polling.
328
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
329
+ `ALTER TABLE log_events ALTER COLUMN cursorId SET DEFAULT nextval('log_events_cursor_id_seq')`,
286
330
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
287
331
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
288
332
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -306,7 +350,9 @@ var ALL_MIGRATIONS = [
306
350
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS tags JSON`,
307
351
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS metadata JSON`,
308
352
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS scope JSON`,
309
- // Scores
353
+ // Scores. Legacy rows remain page-visible but are not part of delta polling.
354
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
355
+ `ALTER TABLE score_events ALTER COLUMN cursorId SET DEFAULT nextval('score_events_cursor_id_seq')`,
310
356
  `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
311
357
  `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
312
358
  `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -334,7 +380,9 @@ var ALL_MIGRATIONS = [
334
380
  `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS source VARCHAR`,
335
381
  `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS scoreSource VARCHAR`,
336
382
  `ALTER TABLE score_events ALTER COLUMN traceId DROP NOT NULL`,
337
- // Feedback
383
+ // Feedback. Legacy rows remain page-visible but are not part of delta polling.
384
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
385
+ `ALTER TABLE feedback_events ALTER COLUMN cursorId SET DEFAULT nextval('feedback_events_cursor_id_seq')`,
338
386
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
339
387
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
340
388
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -598,6 +646,45 @@ function parseJsonArray(value) {
598
646
  const parsed = parseJson(value);
599
647
  return Array.isArray(parsed) ? parsed : null;
600
648
  }
649
+ var OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
650
+ function deltaPollingFeatureEnabled() {
651
+ return coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
652
+ }
653
+ function assertDeltaPollingEnabled() {
654
+ if (deltaPollingFeatureEnabled()) {
655
+ return;
656
+ }
657
+ throw new MastraError({
658
+ id: "OBSERVABILITY_DELTA_POLLING_NOT_SUPPORTED",
659
+ domain: ErrorDomain.MASTRA_OBSERVABILITY,
660
+ category: ErrorCategory.SYSTEM,
661
+ text: "This storage provider does not support observability delta polling"
662
+ });
663
+ }
664
+ function encodeDeltaCursor(value) {
665
+ return String(value ?? 0);
666
+ }
667
+ function validateCursorId(cursor) {
668
+ if (!/^\d+$/.test(cursor)) {
669
+ throw new MastraError({
670
+ id: "OBSERVABILITY_INVALID_DELTA_CURSOR",
671
+ domain: ErrorDomain.MASTRA_OBSERVABILITY,
672
+ category: ErrorCategory.USER,
673
+ text: "Invalid observability delta cursor"
674
+ });
675
+ }
676
+ return cursor;
677
+ }
678
+ function extendWhereClause(baseClause, extraConditions) {
679
+ const conditions = extraConditions.filter(Boolean);
680
+ if (conditions.length === 0) {
681
+ return baseClause;
682
+ }
683
+ if (!baseClause) {
684
+ return `WHERE ${conditions.join(" AND ")}`;
685
+ }
686
+ return `${baseClause} AND ${conditions.join(" AND ")}`;
687
+ }
601
688
 
602
689
  // src/storage/domains/observability/feedback.ts
603
690
  var FEEDBACK_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([
@@ -897,15 +984,41 @@ async function batchCreateFeedback(db, args) {
897
984
  );
898
985
  }
899
986
  async function listFeedback(db, args) {
900
- const filters = args.filters ?? {};
901
- const page = Number(args.pagination?.page ?? 0);
902
- const perPage = Number(args.pagination?.perPage ?? 10);
903
- const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
987
+ const { mode, filters, pagination, orderBy, after, limit } = listFeedbackArgsSchema.parse(args);
988
+ const page = Number(pagination.page);
989
+ const perPage = Number(pagination.perPage);
904
990
  const { clause: filterClause, params: filterParams } = buildWhereClause(filters, {
905
991
  source: "feedbackSource"
906
992
  });
993
+ if (mode === "delta") {
994
+ assertDeltaPollingEnabled();
995
+ const streamHeadCursor = await getStreamHeadCursor(db);
996
+ if (after === void 0) {
997
+ return {
998
+ feedback: [],
999
+ delta: { limit, hasMore: false },
1000
+ deltaCursor: streamHeadCursor
1001
+ };
1002
+ }
1003
+ const afterCursorId = validateCursorId(after);
1004
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1005
+ const rows2 = await db.query(
1006
+ `SELECT * FROM feedback_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1007
+ [...filterParams, afterCursorId, limit + 1]
1008
+ );
1009
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1010
+ cursorId: row.cursorId,
1011
+ feedback: rowToFeedbackRecord(row)
1012
+ }));
1013
+ return {
1014
+ feedback: visibleRows.map((row) => row.feedback),
1015
+ delta: { limit, hasMore: rows2.length > limit },
1016
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1017
+ };
1018
+ }
907
1019
  const orderByClause = buildOrderByClause(orderBy);
908
1020
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1021
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor(db, filterClause, filterParams) : void 0;
909
1022
  const countResult = await db.query(
910
1023
  `SELECT COUNT(*) as total FROM feedback_events ${filterClause}`,
911
1024
  filterParams
@@ -917,9 +1030,26 @@ async function listFeedback(db, args) {
917
1030
  );
918
1031
  return {
919
1032
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
920
- feedback: rows.map((row) => rowToFeedbackRecord(row))
1033
+ feedback: rows.map((row) => rowToFeedbackRecord(row)),
1034
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
921
1035
  };
922
1036
  }
1037
+ async function getDeltaCursor(db, filterClause, filterParams) {
1038
+ const rows = await db.query(
1039
+ `SELECT max(cursorId) AS cursorId FROM feedback_events ${filterClause}`,
1040
+ filterParams
1041
+ );
1042
+ const cursorId = rows[0]?.cursorId;
1043
+ if (cursorId !== null && cursorId !== void 0) {
1044
+ return encodeDeltaCursor(cursorId);
1045
+ }
1046
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`);
1047
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1048
+ }
1049
+ async function getStreamHeadCursor(db) {
1050
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`);
1051
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1052
+ }
923
1053
  async function getFeedbackAggregate(db, args) {
924
1054
  const aggSql = getAggregationSql(args.aggregation);
925
1055
  const { clause, params } = buildFeedbackWhereClause(args, true);
@@ -1047,8 +1177,6 @@ async function getFeedbackPercentiles(db, args) {
1047
1177
  }
1048
1178
  return { series };
1049
1179
  }
1050
-
1051
- // src/storage/domains/observability/logs.ts
1052
1180
  var COLUMNS = [
1053
1181
  "logId",
1054
1182
  "timestamp",
@@ -1164,13 +1292,40 @@ async function batchCreateLogs(db, args) {
1164
1292
  await db.execute(`INSERT INTO log_events (${COLUMNS_SQL}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING`);
1165
1293
  }
1166
1294
  async function listLogs(db, args) {
1167
- const filters = args.filters ?? {};
1168
- const page = Number(args.pagination?.page ?? 0);
1169
- const perPage = Number(args.pagination?.perPage ?? 10);
1170
- const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
1171
- const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
1295
+ const { mode, filters, pagination, orderBy, after, limit } = listLogsArgsSchema.parse(args);
1296
+ const filterRecord = filters;
1297
+ const page = Number(pagination.page);
1298
+ const perPage = Number(pagination.perPage);
1299
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1300
+ if (mode === "delta") {
1301
+ assertDeltaPollingEnabled();
1302
+ const streamHeadCursor = await getStreamHeadCursor2(db);
1303
+ if (after === void 0) {
1304
+ return {
1305
+ logs: [],
1306
+ delta: { limit, hasMore: false },
1307
+ deltaCursor: streamHeadCursor
1308
+ };
1309
+ }
1310
+ const afterCursorId = validateCursorId(after);
1311
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1312
+ const rows2 = await db.query(
1313
+ `SELECT * FROM log_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1314
+ [...filterParams, afterCursorId, limit + 1]
1315
+ );
1316
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1317
+ cursorId: row.cursorId,
1318
+ log: rowToLogRecord(row)
1319
+ }));
1320
+ return {
1321
+ logs: visibleRows.map((row) => row.log),
1322
+ delta: { limit, hasMore: rows2.length > limit },
1323
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1324
+ };
1325
+ }
1172
1326
  const orderByClause = buildOrderByClause(orderBy);
1173
1327
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1328
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor2(db, filterClause, filterParams) : void 0;
1174
1329
  const countResult = await db.query(
1175
1330
  `SELECT COUNT(*) as total FROM log_events ${filterClause}`,
1176
1331
  filterParams
@@ -1183,9 +1338,26 @@ async function listLogs(db, args) {
1183
1338
  const logs = rows.map((row) => rowToLogRecord(row));
1184
1339
  return {
1185
1340
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
1186
- logs
1341
+ logs,
1342
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1187
1343
  };
1188
1344
  }
1345
+ async function getDeltaCursor2(db, filterClause, filterParams) {
1346
+ const rows = await db.query(
1347
+ `SELECT max(cursorId) AS cursorId FROM log_events ${filterClause}`,
1348
+ filterParams
1349
+ );
1350
+ const cursorId = rows[0]?.cursorId;
1351
+ if (cursorId !== null && cursorId !== void 0) {
1352
+ return encodeDeltaCursor(cursorId);
1353
+ }
1354
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`);
1355
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1356
+ }
1357
+ async function getStreamHeadCursor2(db) {
1358
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`);
1359
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1360
+ }
1189
1361
  function resolveDistinctColumnSql(distinctColumn) {
1190
1362
  if (!distinctColumn) {
1191
1363
  throw new Error(`count_distinct aggregation requires a 'distinctColumn' argument`);
@@ -1433,13 +1605,40 @@ async function batchCreateMetrics(db, args) {
1433
1605
  );
1434
1606
  }
1435
1607
  async function listMetrics(db, args) {
1436
- const filters = args.filters ?? {};
1437
- const page = Number(args.pagination?.page ?? 0);
1438
- const perPage = Number(args.pagination?.perPage ?? 10);
1439
- const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
1440
- const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
1608
+ const { mode, filters, pagination, orderBy, after, limit } = listMetricsArgsSchema.parse(args);
1609
+ const filterRecord = filters;
1610
+ const page = Number(pagination.page);
1611
+ const perPage = Number(pagination.perPage);
1612
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1613
+ if (mode === "delta") {
1614
+ assertDeltaPollingEnabled();
1615
+ const streamHeadCursor = await getStreamHeadCursor3(db);
1616
+ if (after === void 0) {
1617
+ return {
1618
+ metrics: [],
1619
+ delta: { limit, hasMore: false },
1620
+ deltaCursor: streamHeadCursor
1621
+ };
1622
+ }
1623
+ const afterCursorId = validateCursorId(after);
1624
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1625
+ const rows2 = await db.query(
1626
+ `SELECT * FROM metric_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1627
+ [...filterParams, afterCursorId, limit + 1]
1628
+ );
1629
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1630
+ cursorId: row.cursorId,
1631
+ metric: rowToMetricRecord(row)
1632
+ }));
1633
+ return {
1634
+ metrics: visibleRows.map((row) => row.metric),
1635
+ delta: { limit, hasMore: rows2.length > limit },
1636
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1637
+ };
1638
+ }
1441
1639
  const orderByClause = buildOrderByClause(orderBy);
1442
1640
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1641
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor3(db, filterClause, filterParams) : void 0;
1443
1642
  const countResult = await db.query(
1444
1643
  `SELECT COUNT(*) AS total FROM metric_events ${filterClause}`,
1445
1644
  filterParams
@@ -1451,9 +1650,26 @@ async function listMetrics(db, args) {
1451
1650
  );
1452
1651
  return {
1453
1652
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
1454
- metrics: rows.map((row) => rowToMetricRecord(row))
1653
+ metrics: rows.map((row) => rowToMetricRecord(row)),
1654
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1455
1655
  };
1456
1656
  }
1657
+ async function getDeltaCursor3(db, filterClause, filterParams) {
1658
+ const rows = await db.query(
1659
+ `SELECT max(cursorId) AS cursorId FROM metric_events ${filterClause}`,
1660
+ filterParams
1661
+ );
1662
+ const cursorId = rows[0]?.cursorId;
1663
+ if (cursorId !== null && cursorId !== void 0) {
1664
+ return encodeDeltaCursor(cursorId);
1665
+ }
1666
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`);
1667
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1668
+ }
1669
+ async function getStreamHeadCursor3(db) {
1670
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`);
1671
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1672
+ }
1457
1673
  async function getMetricAggregate(db, args) {
1458
1674
  const aggSql = getAggregationSql2(args.aggregation, "value", args.distinctColumn);
1459
1675
  const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
@@ -1733,10 +1949,30 @@ async function getMetricLabelValues(db, args) {
1733
1949
  return { values: rows.map((r) => r.val) };
1734
1950
  }
1735
1951
  var SIGNAL_MIGRATIONS = [
1736
- { table: "metric_events", createDDL: METRIC_EVENTS_DDL, idColumn: "metricId" },
1737
- { table: "log_events", createDDL: LOG_EVENTS_DDL, idColumn: "logId" },
1738
- { table: "score_events", createDDL: SCORE_EVENTS_DDL, idColumn: "scoreId" },
1739
- { table: "feedback_events", createDDL: FEEDBACK_EVENTS_DDL, idColumn: "feedbackId" }
1952
+ {
1953
+ table: "metric_events",
1954
+ createDDL: METRIC_EVENTS_DDL,
1955
+ idColumn: "metricId",
1956
+ cursorSequenceDDL: METRIC_EVENTS_CURSOR_SEQUENCE_DDL
1957
+ },
1958
+ {
1959
+ table: "log_events",
1960
+ createDDL: LOG_EVENTS_DDL,
1961
+ idColumn: "logId",
1962
+ cursorSequenceDDL: LOG_EVENTS_CURSOR_SEQUENCE_DDL
1963
+ },
1964
+ {
1965
+ table: "score_events",
1966
+ createDDL: SCORE_EVENTS_DDL,
1967
+ idColumn: "scoreId",
1968
+ cursorSequenceDDL: SCORE_EVENTS_CURSOR_SEQUENCE_DDL
1969
+ },
1970
+ {
1971
+ table: "feedback_events",
1972
+ createDDL: FEEDBACK_EVENTS_DDL,
1973
+ idColumn: "feedbackId",
1974
+ cursorSequenceDDL: FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL
1975
+ }
1740
1976
  ];
1741
1977
  async function tableExists(db, table) {
1742
1978
  const rows = await db.query(
@@ -1796,7 +2032,7 @@ async function checkSignalTablesMigrationStatus(db) {
1796
2032
  };
1797
2033
  }
1798
2034
  async function migrateSignalTables(db, logger) {
1799
- for (const { table, createDDL, idColumn } of SIGNAL_MIGRATIONS) {
2035
+ for (const { table, createDDL, idColumn, cursorSequenceDDL } of SIGNAL_MIGRATIONS) {
1800
2036
  if (!await tableExists(db, table)) continue;
1801
2037
  if (await hasPrimaryKey(db, table)) continue;
1802
2038
  logger?.info?.(`Migrating ${table} to schema with ${idColumn} PRIMARY KEY`);
@@ -1805,6 +2041,7 @@ async function migrateSignalTables(db, logger) {
1805
2041
  let originalRenamed = false;
1806
2042
  let swapCompleted = false;
1807
2043
  try {
2044
+ await db.execute(cursorSequenceDDL);
1808
2045
  await db.execute(buildTemporaryTableDDL(createDDL, table, temp));
1809
2046
  const newColumns = await getColumns(db, temp);
1810
2047
  const currentColumns = new Set(await getColumns(db, table));
@@ -2136,15 +2373,41 @@ async function batchCreateScores(db, args) {
2136
2373
  );
2137
2374
  }
2138
2375
  async function listScores(db, args) {
2139
- const filters = args.filters ?? {};
2140
- const page = Number(args.pagination?.page ?? 0);
2141
- const perPage = Number(args.pagination?.perPage ?? 10);
2142
- const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
2376
+ const { mode, filters, pagination, orderBy, after, limit } = listScoresArgsSchema.parse(args);
2377
+ const page = Number(pagination.page);
2378
+ const perPage = Number(pagination.perPage);
2143
2379
  const { clause: filterClause, params: filterParams } = buildWhereClause(filters, {
2144
2380
  source: "scoreSource"
2145
2381
  });
2382
+ if (mode === "delta") {
2383
+ assertDeltaPollingEnabled();
2384
+ const streamHeadCursor = await getStreamHeadCursor4(db);
2385
+ if (after === void 0) {
2386
+ return {
2387
+ scores: [],
2388
+ delta: { limit, hasMore: false },
2389
+ deltaCursor: streamHeadCursor
2390
+ };
2391
+ }
2392
+ const afterCursorId = validateCursorId(after);
2393
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
2394
+ const rows2 = await db.query(
2395
+ `SELECT * FROM score_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
2396
+ [...filterParams, afterCursorId, limit + 1]
2397
+ );
2398
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
2399
+ cursorId: row.cursorId,
2400
+ score: rowToScoreRecord(row)
2401
+ }));
2402
+ return {
2403
+ scores: visibleRows.map((row) => row.score),
2404
+ delta: { limit, hasMore: rows2.length > limit },
2405
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
2406
+ };
2407
+ }
2146
2408
  const orderByClause = buildOrderByClause(orderBy);
2147
2409
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2410
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor4(db, filterClause, filterParams) : void 0;
2148
2411
  const countResult = await db.query(
2149
2412
  `SELECT COUNT(*) as total FROM score_events ${filterClause}`,
2150
2413
  filterParams
@@ -2156,9 +2419,26 @@ async function listScores(db, args) {
2156
2419
  );
2157
2420
  return {
2158
2421
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2159
- scores: rows.map((row) => rowToScoreRecord(row))
2422
+ scores: rows.map((row) => rowToScoreRecord(row)),
2423
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2160
2424
  };
2161
2425
  }
2426
+ async function getDeltaCursor4(db, filterClause, filterParams) {
2427
+ const rows = await db.query(
2428
+ `SELECT max(cursorId) AS cursorId FROM score_events ${filterClause}`,
2429
+ filterParams
2430
+ );
2431
+ const cursorId = rows[0]?.cursorId;
2432
+ if (cursorId !== null && cursorId !== void 0) {
2433
+ return encodeDeltaCursor(cursorId);
2434
+ }
2435
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`);
2436
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
2437
+ }
2438
+ async function getStreamHeadCursor4(db) {
2439
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`);
2440
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
2441
+ }
2162
2442
  async function getScoreById(db, scoreId) {
2163
2443
  const rows = await db.query(`SELECT * FROM score_events WHERE scoreId = ? LIMIT 1`, [
2164
2444
  scoreId
@@ -2777,13 +3057,152 @@ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
2777
3057
  };
2778
3058
  }
2779
3059
  async function listTraces(db, args) {
2780
- return listTraceRows(
2781
- db,
2782
- args,
2783
- SPAN_RECONSTRUCT_SELECT,
2784
- rowToSpanRecord,
2785
- (spans) => toTraceSpans(spans)
2786
- );
3060
+ const { mode, filters, pagination, orderBy, after, limit } = listTracesArgsSchema.parse(args);
3061
+ const filterRecord = filters ?? {};
3062
+ const page = Number(pagination.page);
3063
+ const perPage = Number(pagination.perPage);
3064
+ if (mode === "delta") {
3065
+ assertDeltaPollingEnabled();
3066
+ const streamHeadCursor = await getTraceStreamHeadCursor(db);
3067
+ if (after === void 0) {
3068
+ return {
3069
+ spans: [],
3070
+ delta: { limit, hasMore: false },
3071
+ deltaCursor: streamHeadCursor
3072
+ };
3073
+ }
3074
+ const afterCursorId = validateCursorId(after);
3075
+ const { prefilter: prefilter2, postAgg: postAgg2, hasChildError: hasChildError2 } = partitionAnchorFilters(filterRecord);
3076
+ const { clause: prefilterClause2, params: prefilterParams2 } = buildWhereClause(prefilter2);
3077
+ const prefilterParts2 = [
3078
+ `eventType = 'start'`,
3079
+ `parentSpanId IS NULL`,
3080
+ `cursorId IS NOT NULL`,
3081
+ `cursorId > CAST(? AS BIGINT)`
3082
+ ];
3083
+ if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, ""));
3084
+ const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`;
3085
+ const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2);
3086
+ const postAggParts2 = [];
3087
+ if (postAggClause2) postAggParts2.push(postAggClause2.replace(/^WHERE\s+/i, ""));
3088
+ const childErrorClause2 = buildHasChildErrorClause(hasChildError2, "root_spans");
3089
+ if (childErrorClause2) postAggParts2.push(childErrorClause2);
3090
+ const postAggWhere2 = postAggParts2.length > 0 ? `WHERE ${postAggParts2.join(" AND ")}` : "";
3091
+ const outerAlias2 = "outer_root";
3092
+ const dataSql2 = `
3093
+ WITH candidate_roots AS (
3094
+ SELECT traceId, spanId, cursorId
3095
+ FROM span_events AS ${outerAlias2}
3096
+ ${prefilterWhere2}
3097
+ ),
3098
+ root_spans AS (
3099
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3100
+ FROM (
3101
+ ${SPAN_RECONSTRUCT_SELECT}
3102
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3103
+ GROUP BY traceId, spanId
3104
+ ) AS reconstructed
3105
+ INNER JOIN candidate_roots USING (traceId, spanId)
3106
+ )
3107
+ SELECT * FROM root_spans ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ?
3108
+ `;
3109
+ const rows2 = await db.query(dataSql2, [
3110
+ afterCursorId,
3111
+ ...prefilterParams2,
3112
+ ...postAggParams2,
3113
+ limit + 1
3114
+ ]);
3115
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
3116
+ cursorId: row.anchorCursorId,
3117
+ span: rowToSpanRecord(row)
3118
+ }));
3119
+ return {
3120
+ spans: toTraceSpans(visibleRows.map((row) => row.span)),
3121
+ delta: { limit, hasMore: rows2.length > limit },
3122
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
3123
+ };
3124
+ }
3125
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filterRecord);
3126
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3127
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`];
3128
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3129
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3130
+ const outerAlias = "outer_root";
3131
+ const orderDir = orderBy.direction.toUpperCase();
3132
+ if (orderDir !== "ASC" && orderDir !== "DESC") {
3133
+ throw new Error(`Invalid sort direction: ${orderBy.direction}`);
3134
+ }
3135
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getTraceDeltaCursor(db, filters) : void 0;
3136
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
3137
+ const hasPostAggFilters = Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter;
3138
+ if (!hasPostAggFilters) {
3139
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
3140
+ const offset = page * perPage;
3141
+ const countSql2 = `
3142
+ SELECT COUNT(*) as total
3143
+ FROM span_events AS ${outerAlias}
3144
+ ${prefilterWhere}
3145
+ `;
3146
+ const countResult2 = await db.query(countSql2, prefilterParams);
3147
+ const total2 = Number(countResult2[0]?.total ?? 0);
3148
+ const pageSql = `
3149
+ WITH page_roots AS (
3150
+ SELECT traceId, spanId
3151
+ FROM span_events AS ${outerAlias}
3152
+ ${prefilterWhere}
3153
+ ${prefilterOrderBy}
3154
+ LIMIT ? OFFSET ?
3155
+ )
3156
+ ${SPAN_RECONSTRUCT_SELECT}
3157
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_roots)
3158
+ GROUP BY traceId, spanId
3159
+ ${buildOrderByClause(orderBy)}
3160
+ `;
3161
+ const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]);
3162
+ const spans2 = rows2.map((row) => rowToSpanRecord(row));
3163
+ return {
3164
+ pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
3165
+ spans: toTraceSpans(spans2),
3166
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3167
+ };
3168
+ }
3169
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3170
+ const postAggParts = [];
3171
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3172
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3173
+ if (childErrorClause) postAggParts.push(childErrorClause);
3174
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3175
+ const cteSql = `
3176
+ WITH candidate_roots AS (
3177
+ SELECT traceId, spanId
3178
+ FROM span_events AS ${outerAlias}
3179
+ ${prefilterWhere}
3180
+ ),
3181
+ root_spans AS (
3182
+ ${SPAN_RECONSTRUCT_SELECT}
3183
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3184
+ GROUP BY traceId, spanId
3185
+ )
3186
+ `;
3187
+ const orderByClause = buildOrderByClause(orderBy);
3188
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
3189
+ const countSql = `
3190
+ ${cteSql}
3191
+ SELECT COUNT(*) as total FROM root_spans ${postAggWhere}
3192
+ `;
3193
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
3194
+ const total = Number(countResult[0]?.total ?? 0);
3195
+ const dataSql = `
3196
+ ${cteSql}
3197
+ SELECT * FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause}
3198
+ `;
3199
+ const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]);
3200
+ const spans = rows.map((row) => rowToSpanRecord(row));
3201
+ return {
3202
+ pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
3203
+ spans: toTraceSpans(spans),
3204
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3205
+ };
2787
3206
  }
2788
3207
  async function listTracesLight(db, args) {
2789
3208
  return listTraceRows(
@@ -2812,18 +3231,88 @@ async function getSpans(db, args) {
2812
3231
  };
2813
3232
  }
2814
3233
  async function listBranches(db, args) {
2815
- const filters = args.filters ?? {};
2816
- const page = Number(args.pagination?.page ?? 0);
2817
- const perPage = Number(args.pagination?.perPage ?? 10);
2818
- const orderBy = { field: args.orderBy?.field ?? "startedAt", direction: args.orderBy?.direction ?? "DESC" };
2819
- const userSpanType = filters.spanType;
3234
+ const { mode, filters, pagination, orderBy, after, limit } = listBranchesArgsSchema.parse(args);
3235
+ const filterRecord = filters ?? {};
3236
+ const page = Number(pagination.page);
3237
+ const perPage = Number(pagination.perPage);
3238
+ const userSpanType = filterRecord.spanType;
2820
3239
  if (typeof userSpanType === "string" && !BRANCH_SPAN_TYPES.includes(userSpanType)) {
3240
+ const currentDeltaCursor2 = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
3241
+ if (mode === "delta") {
3242
+ assertDeltaPollingEnabled();
3243
+ return {
3244
+ branches: [],
3245
+ delta: { limit, hasMore: false },
3246
+ deltaCursor: currentDeltaCursor2
3247
+ };
3248
+ }
2821
3249
  return {
2822
3250
  pagination: { total: 0, page, perPage, hasMore: false },
2823
- branches: []
3251
+ branches: [],
3252
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor2 } : {}
3253
+ };
3254
+ }
3255
+ if (mode === "delta") {
3256
+ assertDeltaPollingEnabled();
3257
+ const streamHeadCursor = await getBranchStreamHeadCursor(
3258
+ db,
3259
+ typeof userSpanType === "string" ? userSpanType : null
3260
+ );
3261
+ if (after === void 0) {
3262
+ return {
3263
+ branches: [],
3264
+ delta: { limit, hasMore: false },
3265
+ deltaCursor: streamHeadCursor
3266
+ };
3267
+ }
3268
+ const afterCursorId = validateCursorId(after);
3269
+ const { spanType: _spanType2, ...rest2 } = filterRecord;
3270
+ const { prefilter: prefilter2, postAgg: postAgg2} = partitionAnchorFilters(rest2);
3271
+ const { clause: prefilterClause2, params: prefilterFilterParams2 } = buildWhereClause(prefilter2);
3272
+ const prefilterParts2 = [`eventType = 'start'`, `cursorId IS NOT NULL`, `cursorId > CAST(? AS BIGINT)`];
3273
+ let spanTypeParams2;
3274
+ if (typeof userSpanType === "string") {
3275
+ prefilterParts2.push(`spanType = ?`);
3276
+ spanTypeParams2 = [userSpanType];
3277
+ } else {
3278
+ prefilterParts2.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3279
+ spanTypeParams2 = [...BRANCH_SPAN_TYPES];
3280
+ }
3281
+ if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, ""));
3282
+ const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`;
3283
+ const prefilterParams2 = [afterCursorId, ...spanTypeParams2, ...prefilterFilterParams2];
3284
+ const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2);
3285
+ const postAggWhere2 = postAggClause2 ? postAggClause2 : "";
3286
+ const outerAlias2 = "outer_anchor";
3287
+ const dataSql2 = `
3288
+ WITH candidate_anchors AS (
3289
+ SELECT traceId, spanId, cursorId
3290
+ FROM span_events AS ${outerAlias2}
3291
+ ${prefilterWhere2}
3292
+ ),
3293
+ branch_anchors AS (
3294
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3295
+ FROM (
3296
+ ${SPAN_RECONSTRUCT_SELECT}
3297
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3298
+ GROUP BY traceId, spanId
3299
+ ) AS reconstructed
3300
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3301
+ )
3302
+ SELECT * FROM branch_anchors ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ?
3303
+ `;
3304
+ const rows2 = await db.query(dataSql2, [...prefilterParams2, ...postAggParams2, limit + 1]);
3305
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
3306
+ cursorId: row.anchorCursorId,
3307
+ branch: rowToSpanRecord(row)
3308
+ }));
3309
+ return {
3310
+ branches: toTraceSpans(visibleRows.map((row) => row.branch)),
3311
+ delta: { limit, hasMore: rows2.length > limit },
3312
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
2824
3313
  };
2825
3314
  }
2826
- const { spanType: _spanType, ...rest } = filters;
3315
+ const { spanType: _spanType, ...rest } = filterRecord;
2827
3316
  const { prefilter, postAgg} = partitionAnchorFilters(rest);
2828
3317
  const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
2829
3318
  const prefilterParts = [`eventType = 'start'`];
@@ -2843,6 +3332,7 @@ async function listBranches(db, args) {
2843
3332
  if (orderDir !== "ASC" && orderDir !== "DESC") {
2844
3333
  throw new Error(`Invalid sort direction: ${orderBy.direction}`);
2845
3334
  }
3335
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
2846
3336
  const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
2847
3337
  const hasPostAggFilters = Object.keys(postAgg).length > 0 || !canOrderInPrefilter;
2848
3338
  if (!hasPostAggFilters) {
@@ -2858,7 +3348,8 @@ async function listBranches(db, args) {
2858
3348
  if (total2 === 0) {
2859
3349
  return {
2860
3350
  pagination: { total: 0, page, perPage, hasMore: false },
2861
- branches: []
3351
+ branches: [],
3352
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2862
3353
  };
2863
3354
  }
2864
3355
  const pageSql = `
@@ -2878,7 +3369,8 @@ async function listBranches(db, args) {
2878
3369
  const spans2 = rows2.map((row) => rowToSpanRecord(row));
2879
3370
  return {
2880
3371
  pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
2881
- branches: toTraceSpans(spans2)
3372
+ branches: toTraceSpans(spans2),
3373
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2882
3374
  };
2883
3375
  }
2884
3376
  const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
@@ -2906,7 +3398,8 @@ async function listBranches(db, args) {
2906
3398
  if (total === 0) {
2907
3399
  return {
2908
3400
  pagination: { total: 0, page, perPage, hasMore: false },
2909
- branches: []
3401
+ branches: [],
3402
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2910
3403
  };
2911
3404
  }
2912
3405
  const dataSql = `
@@ -2917,9 +3410,151 @@ async function listBranches(db, args) {
2917
3410
  const spans = rows.map((row) => rowToSpanRecord(row));
2918
3411
  return {
2919
3412
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2920
- branches: toTraceSpans(spans)
3413
+ branches: toTraceSpans(spans),
3414
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2921
3415
  };
2922
3416
  }
3417
+ async function getTraceDeltaCursor(db, filters) {
3418
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters ?? {});
3419
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3420
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`, `cursorId IS NOT NULL`];
3421
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3422
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3423
+ const outerAlias = "outer_root";
3424
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3425
+ const postAggParts = [];
3426
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3427
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3428
+ if (childErrorClause) postAggParts.push(childErrorClause);
3429
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3430
+ if (postAggWhere === "") {
3431
+ const rows2 = await db.query(
3432
+ `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`,
3433
+ prefilterParams
3434
+ );
3435
+ const cursorId2 = rows2[0]?.cursorId;
3436
+ if (cursorId2 !== null && cursorId2 !== void 0) {
3437
+ return encodeDeltaCursor(cursorId2);
3438
+ }
3439
+ const streamRows2 = await db.query(
3440
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3441
+ );
3442
+ return encodeDeltaCursor(streamRows2[0]?.cursorId);
3443
+ }
3444
+ const cteSql = `
3445
+ WITH candidate_roots AS (
3446
+ SELECT traceId, spanId, cursorId
3447
+ FROM span_events AS ${outerAlias}
3448
+ ${prefilterWhere}
3449
+ ),
3450
+ root_spans AS (
3451
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3452
+ FROM (
3453
+ ${SPAN_RECONSTRUCT_SELECT}
3454
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3455
+ GROUP BY traceId, spanId
3456
+ ) AS reconstructed
3457
+ INNER JOIN candidate_roots USING (traceId, spanId)
3458
+ )
3459
+ `;
3460
+ const rows = await db.query(
3461
+ `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM root_spans ${postAggWhere}`,
3462
+ [...prefilterParams, ...postAggParams]
3463
+ );
3464
+ const cursorId = rows[0]?.cursorId;
3465
+ if (cursorId !== null && cursorId !== void 0) {
3466
+ return encodeDeltaCursor(cursorId);
3467
+ }
3468
+ const streamRows = await db.query(
3469
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3470
+ );
3471
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3472
+ }
3473
+ async function getTraceStreamHeadCursor(db) {
3474
+ const streamRows = await db.query(
3475
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3476
+ );
3477
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3478
+ }
3479
+ async function getBranchDeltaCursor(db, filters) {
3480
+ const filterRecord = filters ?? {};
3481
+ const userSpanType = filterRecord.spanType;
3482
+ const { spanType: _spanType, ...rest } = filterRecord;
3483
+ const { prefilter, postAgg } = partitionAnchorFilters(rest);
3484
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
3485
+ const prefilterParts = [`eventType = 'start'`, `cursorId IS NOT NULL`];
3486
+ let spanTypeParams;
3487
+ if (typeof userSpanType === "string") {
3488
+ prefilterParts.push(`spanType = ?`);
3489
+ spanTypeParams = [userSpanType];
3490
+ } else {
3491
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3492
+ spanTypeParams = [...BRANCH_SPAN_TYPES];
3493
+ }
3494
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3495
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3496
+ const prefilterParams = [...spanTypeParams, ...prefilterFilterParams];
3497
+ const outerAlias = "outer_anchor";
3498
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3499
+ if (!postAggClause) {
3500
+ const rows2 = await db.query(
3501
+ `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`,
3502
+ prefilterParams
3503
+ );
3504
+ const cursorId2 = rows2[0]?.cursorId;
3505
+ if (cursorId2 !== null && cursorId2 !== void 0) {
3506
+ return encodeDeltaCursor(cursorId2);
3507
+ }
3508
+ const streamRows2 = await db.query(
3509
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3510
+ [...BRANCH_SPAN_TYPES]
3511
+ );
3512
+ return encodeDeltaCursor(streamRows2[0]?.cursorId);
3513
+ }
3514
+ const cteSql = `
3515
+ WITH candidate_anchors AS (
3516
+ SELECT traceId, spanId, cursorId
3517
+ FROM span_events AS ${outerAlias}
3518
+ ${prefilterWhere}
3519
+ ),
3520
+ branch_anchors AS (
3521
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3522
+ FROM (
3523
+ ${SPAN_RECONSTRUCT_SELECT}
3524
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3525
+ GROUP BY traceId, spanId
3526
+ ) AS reconstructed
3527
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3528
+ )
3529
+ `;
3530
+ const rows = await db.query(
3531
+ `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM branch_anchors ${postAggClause}`,
3532
+ [...prefilterParams, ...postAggParams]
3533
+ );
3534
+ const cursorId = rows[0]?.cursorId;
3535
+ if (cursorId !== null && cursorId !== void 0) {
3536
+ return encodeDeltaCursor(cursorId);
3537
+ }
3538
+ const streamRows = await db.query(
3539
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3540
+ [...BRANCH_SPAN_TYPES]
3541
+ );
3542
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3543
+ }
3544
+ async function getBranchStreamHeadCursor(db, userSpanType) {
3545
+ if (userSpanType) {
3546
+ const rows2 = await db.query(
3547
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType = ? AND cursorId IS NOT NULL`,
3548
+ [userSpanType]
3549
+ );
3550
+ return encodeDeltaCursor(rows2[0]?.cursorId);
3551
+ }
3552
+ const rows = await db.query(
3553
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3554
+ [...BRANCH_SPAN_TYPES]
3555
+ );
3556
+ return encodeDeltaCursor(rows[0]?.cursorId);
3557
+ }
2923
3558
 
2924
3559
  // src/storage/domains/observability/index.ts
2925
3560
  function buildSignalMigrationRequiredMessage(args) {
@@ -3004,6 +3639,12 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3004
3639
  supported: ["event-sourced"]
3005
3640
  };
3006
3641
  }
3642
+ getFeatures() {
3643
+ if (!deltaPollingFeatureEnabled()) {
3644
+ return void 0;
3645
+ }
3646
+ return ["delta-polling"];
3647
+ }
3007
3648
  // Tracing
3008
3649
  async createSpan(args) {
3009
3650
  return createSpan(this.db, args);
@@ -3140,5 +3781,5 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3140
3781
  };
3141
3782
 
3142
3783
  export { ObservabilityStorageDuckDB };
3143
- //# sourceMappingURL=observability-TS5QIHIC.js.map
3144
- //# sourceMappingURL=observability-TS5QIHIC.js.map
3784
+ //# sourceMappingURL=observability-2MIUMLQP.js.map
3785
+ //# sourceMappingURL=observability-2MIUMLQP.js.map