@mastra/duckdb 1.3.2 → 1.4.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 (29) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/docs/SKILL.md +1 -1
  3. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  4. package/dist/index.cjs +10 -1
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +10 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/{observability-TS5QIHIC.js → observability-M35AJUGT.js} +722 -60
  9. package/dist/observability-M35AJUGT.js.map +1 -0
  10. package/dist/{observability-J6N3H7W7.cjs → observability-V2KYD7UF.cjs} +721 -59
  11. package/dist/observability-V2KYD7UF.cjs.map +1 -0
  12. package/dist/storage/domains/observability/ddl.d.ts +10 -5
  13. package/dist/storage/domains/observability/ddl.d.ts.map +1 -1
  14. package/dist/storage/domains/observability/feedback.d.ts.map +1 -1
  15. package/dist/storage/domains/observability/index.d.ts +1 -0
  16. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  17. package/dist/storage/domains/observability/logs.d.ts.map +1 -1
  18. package/dist/storage/domains/observability/metrics.d.ts.map +1 -1
  19. package/dist/storage/domains/observability/migration.d.ts +14 -0
  20. package/dist/storage/domains/observability/migration.d.ts.map +1 -1
  21. package/dist/storage/domains/observability/polling.d.ts +7 -0
  22. package/dist/storage/domains/observability/polling.d.ts.map +1 -0
  23. package/dist/storage/domains/observability/scores.d.ts.map +1 -1
  24. package/dist/storage/domains/observability/tracing.d.ts.map +1 -1
  25. package/dist/storage/index.d.ts +1 -0
  26. package/dist/storage/index.d.ts.map +1 -1
  27. package/package.json +5 -5
  28. package/dist/observability-J6N3H7W7.cjs.map +0 -1
  29. package/dist/observability-TS5QIHIC.js.map +0 -1
@@ -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,
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,
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,
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,
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,
209
230
 
210
231
  -- IDs
211
232
  feedbackId VARCHAR NOT NULL PRIMARY KEY,
@@ -254,11 +275,34 @@ 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
+ // Existing rows intentionally keep NULL cursorId values; delta polling only
297
+ // applies to rows written by insert paths that explicitly call nextval().
298
+ // Databases upgraded from a prior version may still carry a
299
+ // `DEFAULT nextval(...)` on cursorId, which breaks DuckDB WAL replay; that
300
+ // remediation lives in `dropLegacyCursorIdDefaults` and only runs when the
301
+ // bad default is detected in information_schema.
302
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
260
303
  `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
261
- // Metrics
304
+ // Metrics. Legacy rows remain page-visible but are not part of delta polling.
305
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
262
306
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
263
307
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
264
308
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -282,7 +326,8 @@ var ALL_MIGRATIONS = [
282
326
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS costMetadata JSON`,
283
327
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS metadata JSON`,
284
328
  `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS scope JSON`,
285
- // Logs
329
+ // Logs. Legacy rows remain page-visible but are not part of delta polling.
330
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
286
331
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
287
332
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
288
333
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -306,7 +351,8 @@ var ALL_MIGRATIONS = [
306
351
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS tags JSON`,
307
352
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS metadata JSON`,
308
353
  `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS scope JSON`,
309
- // Scores
354
+ // Scores. Legacy rows remain page-visible but are not part of delta polling.
355
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
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,8 @@ 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`,
338
385
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
339
386
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
340
387
  `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
@@ -598,6 +645,45 @@ function parseJsonArray(value) {
598
645
  const parsed = parseJson(value);
599
646
  return Array.isArray(parsed) ? parsed : null;
600
647
  }
648
+ var OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
649
+ function deltaPollingFeatureEnabled() {
650
+ return coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
651
+ }
652
+ function assertDeltaPollingEnabled() {
653
+ if (deltaPollingFeatureEnabled()) {
654
+ return;
655
+ }
656
+ throw new MastraError({
657
+ id: "OBSERVABILITY_DELTA_POLLING_NOT_SUPPORTED",
658
+ domain: ErrorDomain.MASTRA_OBSERVABILITY,
659
+ category: ErrorCategory.SYSTEM,
660
+ text: "This storage provider does not support observability delta polling"
661
+ });
662
+ }
663
+ function encodeDeltaCursor(value) {
664
+ return String(value ?? 0);
665
+ }
666
+ function validateCursorId(cursor) {
667
+ if (!/^\d+$/.test(cursor)) {
668
+ throw new MastraError({
669
+ id: "OBSERVABILITY_INVALID_DELTA_CURSOR",
670
+ domain: ErrorDomain.MASTRA_OBSERVABILITY,
671
+ category: ErrorCategory.USER,
672
+ text: "Invalid observability delta cursor"
673
+ });
674
+ }
675
+ return cursor;
676
+ }
677
+ function extendWhereClause(baseClause, extraConditions) {
678
+ const conditions = extraConditions.filter(Boolean);
679
+ if (conditions.length === 0) {
680
+ return baseClause;
681
+ }
682
+ if (!baseClause) {
683
+ return `WHERE ${conditions.join(" AND ")}`;
684
+ }
685
+ return `${baseClause} AND ${conditions.join(" AND ")}`;
686
+ }
601
687
 
602
688
  // src/storage/domains/observability/feedback.ts
603
689
  var FEEDBACK_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([
@@ -794,7 +880,7 @@ async function createFeedback(db, args) {
794
880
  const feedbackUserId = f.feedbackUserId ?? f.userId ?? null;
795
881
  await db.execute(
796
882
  `INSERT INTO feedback_events (
797
- feedbackId, timestamp, traceId, spanId, experimentId,
883
+ feedbackId, timestamp, cursorId, traceId, spanId, experimentId,
798
884
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
799
885
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
800
886
  feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope
@@ -802,6 +888,7 @@ async function createFeedback(db, args) {
802
888
  VALUES (${[
803
889
  v(f.feedbackId),
804
890
  v(f.timestamp),
891
+ "nextval('feedback_events_cursor_id_seq')",
805
892
  v(f.traceId),
806
893
  v(f.spanId ?? null),
807
894
  v(f.experimentId ?? null),
@@ -849,6 +936,7 @@ async function batchCreateFeedback(db, args) {
849
936
  return `(${[
850
937
  v(legacyFeedback.feedbackId),
851
938
  v(legacyFeedback.timestamp),
939
+ "nextval('feedback_events_cursor_id_seq')",
852
940
  v(legacyFeedback.traceId),
853
941
  v(legacyFeedback.spanId ?? null),
854
942
  v(legacyFeedback.experimentId ?? null),
@@ -887,7 +975,7 @@ async function batchCreateFeedback(db, args) {
887
975
  });
888
976
  await db.execute(
889
977
  `INSERT INTO feedback_events (
890
- feedbackId, timestamp, traceId, spanId, experimentId,
978
+ feedbackId, timestamp, cursorId, traceId, spanId, experimentId,
891
979
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
892
980
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
893
981
  feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope
@@ -897,15 +985,41 @@ async function batchCreateFeedback(db, args) {
897
985
  );
898
986
  }
899
987
  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" };
988
+ const { mode, filters, pagination, orderBy, after, limit } = listFeedbackArgsSchema.parse(args);
989
+ const page = Number(pagination.page);
990
+ const perPage = Number(pagination.perPage);
904
991
  const { clause: filterClause, params: filterParams } = buildWhereClause(filters, {
905
992
  source: "feedbackSource"
906
993
  });
994
+ if (mode === "delta") {
995
+ assertDeltaPollingEnabled();
996
+ const streamHeadCursor = await getStreamHeadCursor(db);
997
+ if (after === void 0) {
998
+ return {
999
+ feedback: [],
1000
+ delta: { limit, hasMore: false },
1001
+ deltaCursor: streamHeadCursor
1002
+ };
1003
+ }
1004
+ const afterCursorId = validateCursorId(after);
1005
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1006
+ const rows2 = await db.query(
1007
+ `SELECT * FROM feedback_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1008
+ [...filterParams, afterCursorId, limit + 1]
1009
+ );
1010
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1011
+ cursorId: row.cursorId,
1012
+ feedback: rowToFeedbackRecord(row)
1013
+ }));
1014
+ return {
1015
+ feedback: visibleRows.map((row) => row.feedback),
1016
+ delta: { limit, hasMore: rows2.length > limit },
1017
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1018
+ };
1019
+ }
907
1020
  const orderByClause = buildOrderByClause(orderBy);
908
1021
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1022
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor(db, filterClause, filterParams) : void 0;
909
1023
  const countResult = await db.query(
910
1024
  `SELECT COUNT(*) as total FROM feedback_events ${filterClause}`,
911
1025
  filterParams
@@ -917,9 +1031,26 @@ async function listFeedback(db, args) {
917
1031
  );
918
1032
  return {
919
1033
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
920
- feedback: rows.map((row) => rowToFeedbackRecord(row))
1034
+ feedback: rows.map((row) => rowToFeedbackRecord(row)),
1035
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
921
1036
  };
922
1037
  }
1038
+ async function getDeltaCursor(db, filterClause, filterParams) {
1039
+ const rows = await db.query(
1040
+ `SELECT max(cursorId) AS cursorId FROM feedback_events ${filterClause}`,
1041
+ filterParams
1042
+ );
1043
+ const cursorId = rows[0]?.cursorId;
1044
+ if (cursorId !== null && cursorId !== void 0) {
1045
+ return encodeDeltaCursor(cursorId);
1046
+ }
1047
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`);
1048
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1049
+ }
1050
+ async function getStreamHeadCursor(db) {
1051
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`);
1052
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1053
+ }
923
1054
  async function getFeedbackAggregate(db, args) {
924
1055
  const aggSql = getAggregationSql(args.aggregation);
925
1056
  const { clause, params } = buildFeedbackWhereClause(args, true);
@@ -1047,11 +1178,10 @@ async function getFeedbackPercentiles(db, args) {
1047
1178
  }
1048
1179
  return { series };
1049
1180
  }
1050
-
1051
- // src/storage/domains/observability/logs.ts
1052
1181
  var COLUMNS = [
1053
1182
  "logId",
1054
1183
  "timestamp",
1184
+ "cursorId",
1055
1185
  "level",
1056
1186
  "message",
1057
1187
  "data",
@@ -1128,6 +1258,7 @@ async function batchCreateLogs(db, args) {
1128
1258
  return `(${[
1129
1259
  v(log.logId),
1130
1260
  v(log.timestamp),
1261
+ "nextval('log_events_cursor_id_seq')",
1131
1262
  v(log.level),
1132
1263
  v(log.message),
1133
1264
  jsonV(log.data),
@@ -1164,13 +1295,40 @@ async function batchCreateLogs(db, args) {
1164
1295
  await db.execute(`INSERT INTO log_events (${COLUMNS_SQL}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING`);
1165
1296
  }
1166
1297
  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);
1298
+ const { mode, filters, pagination, orderBy, after, limit } = listLogsArgsSchema.parse(args);
1299
+ const filterRecord = filters;
1300
+ const page = Number(pagination.page);
1301
+ const perPage = Number(pagination.perPage);
1302
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1303
+ if (mode === "delta") {
1304
+ assertDeltaPollingEnabled();
1305
+ const streamHeadCursor = await getStreamHeadCursor2(db);
1306
+ if (after === void 0) {
1307
+ return {
1308
+ logs: [],
1309
+ delta: { limit, hasMore: false },
1310
+ deltaCursor: streamHeadCursor
1311
+ };
1312
+ }
1313
+ const afterCursorId = validateCursorId(after);
1314
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1315
+ const rows2 = await db.query(
1316
+ `SELECT * FROM log_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1317
+ [...filterParams, afterCursorId, limit + 1]
1318
+ );
1319
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1320
+ cursorId: row.cursorId,
1321
+ log: rowToLogRecord(row)
1322
+ }));
1323
+ return {
1324
+ logs: visibleRows.map((row) => row.log),
1325
+ delta: { limit, hasMore: rows2.length > limit },
1326
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1327
+ };
1328
+ }
1172
1329
  const orderByClause = buildOrderByClause(orderBy);
1173
1330
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1331
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor2(db, filterClause, filterParams) : void 0;
1174
1332
  const countResult = await db.query(
1175
1333
  `SELECT COUNT(*) as total FROM log_events ${filterClause}`,
1176
1334
  filterParams
@@ -1183,9 +1341,26 @@ async function listLogs(db, args) {
1183
1341
  const logs = rows.map((row) => rowToLogRecord(row));
1184
1342
  return {
1185
1343
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
1186
- logs
1344
+ logs,
1345
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1187
1346
  };
1188
1347
  }
1348
+ async function getDeltaCursor2(db, filterClause, filterParams) {
1349
+ const rows = await db.query(
1350
+ `SELECT max(cursorId) AS cursorId FROM log_events ${filterClause}`,
1351
+ filterParams
1352
+ );
1353
+ const cursorId = rows[0]?.cursorId;
1354
+ if (cursorId !== null && cursorId !== void 0) {
1355
+ return encodeDeltaCursor(cursorId);
1356
+ }
1357
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`);
1358
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1359
+ }
1360
+ async function getStreamHeadCursor2(db) {
1361
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`);
1362
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1363
+ }
1189
1364
  function resolveDistinctColumnSql(distinctColumn) {
1190
1365
  if (!distinctColumn) {
1191
1366
  throw new Error(`count_distinct aggregation requires a 'distinctColumn' argument`);
@@ -1242,6 +1417,7 @@ function buildMetricNameFilter(name) {
1242
1417
  var METRIC_COLUMNS = [
1243
1418
  "metricId",
1244
1419
  "timestamp",
1420
+ "cursorId",
1245
1421
  "name",
1246
1422
  "value",
1247
1423
  "traceId",
@@ -1390,6 +1566,7 @@ async function batchCreateMetrics(db, args) {
1390
1566
  return `(${[
1391
1567
  v(m.metricId),
1392
1568
  v(m.timestamp),
1569
+ "nextval('metric_events_cursor_id_seq')",
1393
1570
  v(m.name),
1394
1571
  v(m.value),
1395
1572
  v(m.traceId ?? null),
@@ -1433,13 +1610,40 @@ async function batchCreateMetrics(db, args) {
1433
1610
  );
1434
1611
  }
1435
1612
  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);
1613
+ const { mode, filters, pagination, orderBy, after, limit } = listMetricsArgsSchema.parse(args);
1614
+ const filterRecord = filters;
1615
+ const page = Number(pagination.page);
1616
+ const perPage = Number(pagination.perPage);
1617
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1618
+ if (mode === "delta") {
1619
+ assertDeltaPollingEnabled();
1620
+ const streamHeadCursor = await getStreamHeadCursor3(db);
1621
+ if (after === void 0) {
1622
+ return {
1623
+ metrics: [],
1624
+ delta: { limit, hasMore: false },
1625
+ deltaCursor: streamHeadCursor
1626
+ };
1627
+ }
1628
+ const afterCursorId = validateCursorId(after);
1629
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1630
+ const rows2 = await db.query(
1631
+ `SELECT * FROM metric_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
1632
+ [...filterParams, afterCursorId, limit + 1]
1633
+ );
1634
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
1635
+ cursorId: row.cursorId,
1636
+ metric: rowToMetricRecord(row)
1637
+ }));
1638
+ return {
1639
+ metrics: visibleRows.map((row) => row.metric),
1640
+ delta: { limit, hasMore: rows2.length > limit },
1641
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1642
+ };
1643
+ }
1441
1644
  const orderByClause = buildOrderByClause(orderBy);
1442
1645
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
1646
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor3(db, filterClause, filterParams) : void 0;
1443
1647
  const countResult = await db.query(
1444
1648
  `SELECT COUNT(*) AS total FROM metric_events ${filterClause}`,
1445
1649
  filterParams
@@ -1451,9 +1655,26 @@ async function listMetrics(db, args) {
1451
1655
  );
1452
1656
  return {
1453
1657
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
1454
- metrics: rows.map((row) => rowToMetricRecord(row))
1658
+ metrics: rows.map((row) => rowToMetricRecord(row)),
1659
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1455
1660
  };
1456
1661
  }
1662
+ async function getDeltaCursor3(db, filterClause, filterParams) {
1663
+ const rows = await db.query(
1664
+ `SELECT max(cursorId) AS cursorId FROM metric_events ${filterClause}`,
1665
+ filterParams
1666
+ );
1667
+ const cursorId = rows[0]?.cursorId;
1668
+ if (cursorId !== null && cursorId !== void 0) {
1669
+ return encodeDeltaCursor(cursorId);
1670
+ }
1671
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`);
1672
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1673
+ }
1674
+ async function getStreamHeadCursor3(db) {
1675
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`);
1676
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
1677
+ }
1457
1678
  async function getMetricAggregate(db, args) {
1458
1679
  const aggSql = getAggregationSql2(args.aggregation, "value", args.distinctColumn);
1459
1680
  const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
@@ -1732,11 +1953,42 @@ async function getMetricLabelValues(db, args) {
1732
1953
  );
1733
1954
  return { values: rows.map((r) => r.val) };
1734
1955
  }
1956
+ var CURSOR_ID_TABLES = ["span_events", "metric_events", "log_events", "score_events", "feedback_events"];
1957
+ async function dropLegacyCursorIdDefaults(db) {
1958
+ const rows = await db.query(
1959
+ `SELECT table_name FROM information_schema.columns
1960
+ WHERE column_name = 'cursorId'
1961
+ AND column_default IS NOT NULL
1962
+ AND table_name IN (${CURSOR_ID_TABLES.map((t) => `'${t}'`).join(", ")})`
1963
+ );
1964
+ if (rows.length === 0) return;
1965
+ await db.executeBatch(rows.map((row) => `ALTER TABLE ${row.table_name} ALTER COLUMN cursorId DROP DEFAULT`));
1966
+ }
1735
1967
  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" }
1968
+ {
1969
+ table: "metric_events",
1970
+ createDDL: METRIC_EVENTS_DDL,
1971
+ idColumn: "metricId",
1972
+ cursorSequenceDDL: METRIC_EVENTS_CURSOR_SEQUENCE_DDL
1973
+ },
1974
+ {
1975
+ table: "log_events",
1976
+ createDDL: LOG_EVENTS_DDL,
1977
+ idColumn: "logId",
1978
+ cursorSequenceDDL: LOG_EVENTS_CURSOR_SEQUENCE_DDL
1979
+ },
1980
+ {
1981
+ table: "score_events",
1982
+ createDDL: SCORE_EVENTS_DDL,
1983
+ idColumn: "scoreId",
1984
+ cursorSequenceDDL: SCORE_EVENTS_CURSOR_SEQUENCE_DDL
1985
+ },
1986
+ {
1987
+ table: "feedback_events",
1988
+ createDDL: FEEDBACK_EVENTS_DDL,
1989
+ idColumn: "feedbackId",
1990
+ cursorSequenceDDL: FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL
1991
+ }
1740
1992
  ];
1741
1993
  async function tableExists(db, table) {
1742
1994
  const rows = await db.query(
@@ -1796,7 +2048,7 @@ async function checkSignalTablesMigrationStatus(db) {
1796
2048
  };
1797
2049
  }
1798
2050
  async function migrateSignalTables(db, logger) {
1799
- for (const { table, createDDL, idColumn } of SIGNAL_MIGRATIONS) {
2051
+ for (const { table, createDDL, idColumn, cursorSequenceDDL } of SIGNAL_MIGRATIONS) {
1800
2052
  if (!await tableExists(db, table)) continue;
1801
2053
  if (await hasPrimaryKey(db, table)) continue;
1802
2054
  logger?.info?.(`Migrating ${table} to schema with ${idColumn} PRIMARY KEY`);
@@ -1805,6 +2057,7 @@ async function migrateSignalTables(db, logger) {
1805
2057
  let originalRenamed = false;
1806
2058
  let swapCompleted = false;
1807
2059
  try {
2060
+ await db.execute(cursorSequenceDDL);
1808
2061
  await db.execute(buildTemporaryTableDDL(createDDL, table, temp));
1809
2062
  const newColumns = await getColumns(db, temp);
1810
2063
  const currentColumns = new Set(await getColumns(db, table));
@@ -2034,7 +2287,7 @@ async function createScore(db, args) {
2034
2287
  const scoreSource = s.scoreSource ?? s.source ?? null;
2035
2288
  await db.execute(
2036
2289
  `INSERT INTO score_events (
2037
- scoreId, timestamp, traceId, spanId, experimentId, scoreTraceId,
2290
+ scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2038
2291
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2039
2292
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
2040
2293
  scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope
@@ -2042,6 +2295,7 @@ async function createScore(db, args) {
2042
2295
  VALUES (${[
2043
2296
  v(s.scoreId),
2044
2297
  v(s.timestamp),
2298
+ "nextval('score_events_cursor_id_seq')",
2045
2299
  v(s.traceId),
2046
2300
  v(s.spanId ?? null),
2047
2301
  v(s.experimentId ?? null),
@@ -2088,6 +2342,7 @@ async function batchCreateScores(db, args) {
2088
2342
  return `(${[
2089
2343
  v(legacyScore.scoreId),
2090
2344
  v(legacyScore.timestamp),
2345
+ "nextval('score_events_cursor_id_seq')",
2091
2346
  v(legacyScore.traceId),
2092
2347
  v(legacyScore.spanId ?? null),
2093
2348
  v(legacyScore.experimentId ?? null),
@@ -2126,7 +2381,7 @@ async function batchCreateScores(db, args) {
2126
2381
  });
2127
2382
  await db.execute(
2128
2383
  `INSERT INTO score_events (
2129
- scoreId, timestamp, traceId, spanId, experimentId, scoreTraceId,
2384
+ scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2130
2385
  entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2131
2386
  userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
2132
2387
  scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope
@@ -2136,15 +2391,41 @@ async function batchCreateScores(db, args) {
2136
2391
  );
2137
2392
  }
2138
2393
  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" };
2394
+ const { mode, filters, pagination, orderBy, after, limit } = listScoresArgsSchema.parse(args);
2395
+ const page = Number(pagination.page);
2396
+ const perPage = Number(pagination.perPage);
2143
2397
  const { clause: filterClause, params: filterParams } = buildWhereClause(filters, {
2144
2398
  source: "scoreSource"
2145
2399
  });
2400
+ if (mode === "delta") {
2401
+ assertDeltaPollingEnabled();
2402
+ const streamHeadCursor = await getStreamHeadCursor4(db);
2403
+ if (after === void 0) {
2404
+ return {
2405
+ scores: [],
2406
+ delta: { limit, hasMore: false },
2407
+ deltaCursor: streamHeadCursor
2408
+ };
2409
+ }
2410
+ const afterCursorId = validateCursorId(after);
2411
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
2412
+ const rows2 = await db.query(
2413
+ `SELECT * FROM score_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`,
2414
+ [...filterParams, afterCursorId, limit + 1]
2415
+ );
2416
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
2417
+ cursorId: row.cursorId,
2418
+ score: rowToScoreRecord(row)
2419
+ }));
2420
+ return {
2421
+ scores: visibleRows.map((row) => row.score),
2422
+ delta: { limit, hasMore: rows2.length > limit },
2423
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
2424
+ };
2425
+ }
2146
2426
  const orderByClause = buildOrderByClause(orderBy);
2147
2427
  const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
2428
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor4(db, filterClause, filterParams) : void 0;
2148
2429
  const countResult = await db.query(
2149
2430
  `SELECT COUNT(*) as total FROM score_events ${filterClause}`,
2150
2431
  filterParams
@@ -2156,9 +2437,26 @@ async function listScores(db, args) {
2156
2437
  );
2157
2438
  return {
2158
2439
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2159
- scores: rows.map((row) => rowToScoreRecord(row))
2440
+ scores: rows.map((row) => rowToScoreRecord(row)),
2441
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2160
2442
  };
2161
2443
  }
2444
+ async function getDeltaCursor4(db, filterClause, filterParams) {
2445
+ const rows = await db.query(
2446
+ `SELECT max(cursorId) AS cursorId FROM score_events ${filterClause}`,
2447
+ filterParams
2448
+ );
2449
+ const cursorId = rows[0]?.cursorId;
2450
+ if (cursorId !== null && cursorId !== void 0) {
2451
+ return encodeDeltaCursor(cursorId);
2452
+ }
2453
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`);
2454
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
2455
+ }
2456
+ async function getStreamHeadCursor4(db) {
2457
+ const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`);
2458
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
2459
+ }
2162
2460
  async function getScoreById(db, scoreId) {
2163
2461
  const rows = await db.query(`SELECT * FROM score_events WHERE scoreId = ? LIMIT 1`, [
2164
2462
  scoreId
@@ -2291,6 +2589,7 @@ async function getScorePercentiles(db, args) {
2291
2589
  var COLUMNS2 = [
2292
2590
  "eventType",
2293
2591
  "timestamp",
2592
+ "cursorId",
2294
2593
  "traceId",
2295
2594
  "spanId",
2296
2595
  "parentSpanId",
@@ -2523,6 +2822,7 @@ function toValuesTuple(row) {
2523
2822
  return [
2524
2823
  v(row.eventType),
2525
2824
  v(row.timestamp),
2825
+ "nextval('span_events_cursor_id_seq')",
2526
2826
  v(row.traceId),
2527
2827
  v(row.spanId),
2528
2828
  v(row.parentSpanId),
@@ -2777,13 +3077,152 @@ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
2777
3077
  };
2778
3078
  }
2779
3079
  async function listTraces(db, args) {
2780
- return listTraceRows(
2781
- db,
2782
- args,
2783
- SPAN_RECONSTRUCT_SELECT,
2784
- rowToSpanRecord,
2785
- (spans) => toTraceSpans(spans)
2786
- );
3080
+ const { mode, filters, pagination, orderBy, after, limit } = listTracesArgsSchema.parse(args);
3081
+ const filterRecord = filters ?? {};
3082
+ const page = Number(pagination.page);
3083
+ const perPage = Number(pagination.perPage);
3084
+ if (mode === "delta") {
3085
+ assertDeltaPollingEnabled();
3086
+ const streamHeadCursor = await getTraceStreamHeadCursor(db);
3087
+ if (after === void 0) {
3088
+ return {
3089
+ spans: [],
3090
+ delta: { limit, hasMore: false },
3091
+ deltaCursor: streamHeadCursor
3092
+ };
3093
+ }
3094
+ const afterCursorId = validateCursorId(after);
3095
+ const { prefilter: prefilter2, postAgg: postAgg2, hasChildError: hasChildError2 } = partitionAnchorFilters(filterRecord);
3096
+ const { clause: prefilterClause2, params: prefilterParams2 } = buildWhereClause(prefilter2);
3097
+ const prefilterParts2 = [
3098
+ `eventType = 'start'`,
3099
+ `parentSpanId IS NULL`,
3100
+ `cursorId IS NOT NULL`,
3101
+ `cursorId > CAST(? AS BIGINT)`
3102
+ ];
3103
+ if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, ""));
3104
+ const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`;
3105
+ const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2);
3106
+ const postAggParts2 = [];
3107
+ if (postAggClause2) postAggParts2.push(postAggClause2.replace(/^WHERE\s+/i, ""));
3108
+ const childErrorClause2 = buildHasChildErrorClause(hasChildError2, "root_spans");
3109
+ if (childErrorClause2) postAggParts2.push(childErrorClause2);
3110
+ const postAggWhere2 = postAggParts2.length > 0 ? `WHERE ${postAggParts2.join(" AND ")}` : "";
3111
+ const outerAlias2 = "outer_root";
3112
+ const dataSql2 = `
3113
+ WITH candidate_roots AS (
3114
+ SELECT traceId, spanId, cursorId
3115
+ FROM span_events AS ${outerAlias2}
3116
+ ${prefilterWhere2}
3117
+ ),
3118
+ root_spans AS (
3119
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3120
+ FROM (
3121
+ ${SPAN_RECONSTRUCT_SELECT}
3122
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3123
+ GROUP BY traceId, spanId
3124
+ ) AS reconstructed
3125
+ INNER JOIN candidate_roots USING (traceId, spanId)
3126
+ )
3127
+ SELECT * FROM root_spans ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ?
3128
+ `;
3129
+ const rows2 = await db.query(dataSql2, [
3130
+ afterCursorId,
3131
+ ...prefilterParams2,
3132
+ ...postAggParams2,
3133
+ limit + 1
3134
+ ]);
3135
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
3136
+ cursorId: row.anchorCursorId,
3137
+ span: rowToSpanRecord(row)
3138
+ }));
3139
+ return {
3140
+ spans: toTraceSpans(visibleRows.map((row) => row.span)),
3141
+ delta: { limit, hasMore: rows2.length > limit },
3142
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
3143
+ };
3144
+ }
3145
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filterRecord);
3146
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3147
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`];
3148
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3149
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3150
+ const outerAlias = "outer_root";
3151
+ const orderDir = orderBy.direction.toUpperCase();
3152
+ if (orderDir !== "ASC" && orderDir !== "DESC") {
3153
+ throw new Error(`Invalid sort direction: ${orderBy.direction}`);
3154
+ }
3155
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getTraceDeltaCursor(db, filters) : void 0;
3156
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
3157
+ const hasPostAggFilters = Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter;
3158
+ if (!hasPostAggFilters) {
3159
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
3160
+ const offset = page * perPage;
3161
+ const countSql2 = `
3162
+ SELECT COUNT(*) as total
3163
+ FROM span_events AS ${outerAlias}
3164
+ ${prefilterWhere}
3165
+ `;
3166
+ const countResult2 = await db.query(countSql2, prefilterParams);
3167
+ const total2 = Number(countResult2[0]?.total ?? 0);
3168
+ const pageSql = `
3169
+ WITH page_roots AS (
3170
+ SELECT traceId, spanId
3171
+ FROM span_events AS ${outerAlias}
3172
+ ${prefilterWhere}
3173
+ ${prefilterOrderBy}
3174
+ LIMIT ? OFFSET ?
3175
+ )
3176
+ ${SPAN_RECONSTRUCT_SELECT}
3177
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_roots)
3178
+ GROUP BY traceId, spanId
3179
+ ${buildOrderByClause(orderBy)}
3180
+ `;
3181
+ const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]);
3182
+ const spans2 = rows2.map((row) => rowToSpanRecord(row));
3183
+ return {
3184
+ pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
3185
+ spans: toTraceSpans(spans2),
3186
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3187
+ };
3188
+ }
3189
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3190
+ const postAggParts = [];
3191
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3192
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3193
+ if (childErrorClause) postAggParts.push(childErrorClause);
3194
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3195
+ const cteSql = `
3196
+ WITH candidate_roots AS (
3197
+ SELECT traceId, spanId
3198
+ FROM span_events AS ${outerAlias}
3199
+ ${prefilterWhere}
3200
+ ),
3201
+ root_spans AS (
3202
+ ${SPAN_RECONSTRUCT_SELECT}
3203
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3204
+ GROUP BY traceId, spanId
3205
+ )
3206
+ `;
3207
+ const orderByClause = buildOrderByClause(orderBy);
3208
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
3209
+ const countSql = `
3210
+ ${cteSql}
3211
+ SELECT COUNT(*) as total FROM root_spans ${postAggWhere}
3212
+ `;
3213
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
3214
+ const total = Number(countResult[0]?.total ?? 0);
3215
+ const dataSql = `
3216
+ ${cteSql}
3217
+ SELECT * FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause}
3218
+ `;
3219
+ const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]);
3220
+ const spans = rows.map((row) => rowToSpanRecord(row));
3221
+ return {
3222
+ pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
3223
+ spans: toTraceSpans(spans),
3224
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3225
+ };
2787
3226
  }
2788
3227
  async function listTracesLight(db, args) {
2789
3228
  return listTraceRows(
@@ -2812,18 +3251,88 @@ async function getSpans(db, args) {
2812
3251
  };
2813
3252
  }
2814
3253
  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;
3254
+ const { mode, filters, pagination, orderBy, after, limit } = listBranchesArgsSchema.parse(args);
3255
+ const filterRecord = filters ?? {};
3256
+ const page = Number(pagination.page);
3257
+ const perPage = Number(pagination.perPage);
3258
+ const userSpanType = filterRecord.spanType;
2820
3259
  if (typeof userSpanType === "string" && !BRANCH_SPAN_TYPES.includes(userSpanType)) {
3260
+ const currentDeltaCursor2 = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
3261
+ if (mode === "delta") {
3262
+ assertDeltaPollingEnabled();
3263
+ return {
3264
+ branches: [],
3265
+ delta: { limit, hasMore: false },
3266
+ deltaCursor: currentDeltaCursor2
3267
+ };
3268
+ }
2821
3269
  return {
2822
3270
  pagination: { total: 0, page, perPage, hasMore: false },
2823
- branches: []
3271
+ branches: [],
3272
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor2 } : {}
3273
+ };
3274
+ }
3275
+ if (mode === "delta") {
3276
+ assertDeltaPollingEnabled();
3277
+ const streamHeadCursor = await getBranchStreamHeadCursor(
3278
+ db,
3279
+ typeof userSpanType === "string" ? userSpanType : null
3280
+ );
3281
+ if (after === void 0) {
3282
+ return {
3283
+ branches: [],
3284
+ delta: { limit, hasMore: false },
3285
+ deltaCursor: streamHeadCursor
3286
+ };
3287
+ }
3288
+ const afterCursorId = validateCursorId(after);
3289
+ const { spanType: _spanType2, ...rest2 } = filterRecord;
3290
+ const { prefilter: prefilter2, postAgg: postAgg2} = partitionAnchorFilters(rest2);
3291
+ const { clause: prefilterClause2, params: prefilterFilterParams2 } = buildWhereClause(prefilter2);
3292
+ const prefilterParts2 = [`eventType = 'start'`, `cursorId IS NOT NULL`, `cursorId > CAST(? AS BIGINT)`];
3293
+ let spanTypeParams2;
3294
+ if (typeof userSpanType === "string") {
3295
+ prefilterParts2.push(`spanType = ?`);
3296
+ spanTypeParams2 = [userSpanType];
3297
+ } else {
3298
+ prefilterParts2.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3299
+ spanTypeParams2 = [...BRANCH_SPAN_TYPES];
3300
+ }
3301
+ if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, ""));
3302
+ const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`;
3303
+ const prefilterParams2 = [afterCursorId, ...spanTypeParams2, ...prefilterFilterParams2];
3304
+ const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2);
3305
+ const postAggWhere2 = postAggClause2 ? postAggClause2 : "";
3306
+ const outerAlias2 = "outer_anchor";
3307
+ const dataSql2 = `
3308
+ WITH candidate_anchors AS (
3309
+ SELECT traceId, spanId, cursorId
3310
+ FROM span_events AS ${outerAlias2}
3311
+ ${prefilterWhere2}
3312
+ ),
3313
+ branch_anchors AS (
3314
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3315
+ FROM (
3316
+ ${SPAN_RECONSTRUCT_SELECT}
3317
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3318
+ GROUP BY traceId, spanId
3319
+ ) AS reconstructed
3320
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3321
+ )
3322
+ SELECT * FROM branch_anchors ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ?
3323
+ `;
3324
+ const rows2 = await db.query(dataSql2, [...prefilterParams2, ...postAggParams2, limit + 1]);
3325
+ const visibleRows = rows2.slice(0, limit).map((row) => ({
3326
+ cursorId: row.anchorCursorId,
3327
+ branch: rowToSpanRecord(row)
3328
+ }));
3329
+ return {
3330
+ branches: toTraceSpans(visibleRows.map((row) => row.branch)),
3331
+ delta: { limit, hasMore: rows2.length > limit },
3332
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
2824
3333
  };
2825
3334
  }
2826
- const { spanType: _spanType, ...rest } = filters;
3335
+ const { spanType: _spanType, ...rest } = filterRecord;
2827
3336
  const { prefilter, postAgg} = partitionAnchorFilters(rest);
2828
3337
  const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
2829
3338
  const prefilterParts = [`eventType = 'start'`];
@@ -2843,6 +3352,7 @@ async function listBranches(db, args) {
2843
3352
  if (orderDir !== "ASC" && orderDir !== "DESC") {
2844
3353
  throw new Error(`Invalid sort direction: ${orderBy.direction}`);
2845
3354
  }
3355
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
2846
3356
  const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
2847
3357
  const hasPostAggFilters = Object.keys(postAgg).length > 0 || !canOrderInPrefilter;
2848
3358
  if (!hasPostAggFilters) {
@@ -2858,7 +3368,8 @@ async function listBranches(db, args) {
2858
3368
  if (total2 === 0) {
2859
3369
  return {
2860
3370
  pagination: { total: 0, page, perPage, hasMore: false },
2861
- branches: []
3371
+ branches: [],
3372
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2862
3373
  };
2863
3374
  }
2864
3375
  const pageSql = `
@@ -2878,7 +3389,8 @@ async function listBranches(db, args) {
2878
3389
  const spans2 = rows2.map((row) => rowToSpanRecord(row));
2879
3390
  return {
2880
3391
  pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 },
2881
- branches: toTraceSpans(spans2)
3392
+ branches: toTraceSpans(spans2),
3393
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2882
3394
  };
2883
3395
  }
2884
3396
  const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
@@ -2906,7 +3418,8 @@ async function listBranches(db, args) {
2906
3418
  if (total === 0) {
2907
3419
  return {
2908
3420
  pagination: { total: 0, page, perPage, hasMore: false },
2909
- branches: []
3421
+ branches: [],
3422
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2910
3423
  };
2911
3424
  }
2912
3425
  const dataSql = `
@@ -2917,9 +3430,151 @@ async function listBranches(db, args) {
2917
3430
  const spans = rows.map((row) => rowToSpanRecord(row));
2918
3431
  return {
2919
3432
  pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
2920
- branches: toTraceSpans(spans)
3433
+ branches: toTraceSpans(spans),
3434
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2921
3435
  };
2922
3436
  }
3437
+ async function getTraceDeltaCursor(db, filters) {
3438
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters ?? {});
3439
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3440
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`, `cursorId IS NOT NULL`];
3441
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3442
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3443
+ const outerAlias = "outer_root";
3444
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3445
+ const postAggParts = [];
3446
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3447
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3448
+ if (childErrorClause) postAggParts.push(childErrorClause);
3449
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3450
+ if (postAggWhere === "") {
3451
+ const rows2 = await db.query(
3452
+ `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`,
3453
+ prefilterParams
3454
+ );
3455
+ const cursorId2 = rows2[0]?.cursorId;
3456
+ if (cursorId2 !== null && cursorId2 !== void 0) {
3457
+ return encodeDeltaCursor(cursorId2);
3458
+ }
3459
+ const streamRows2 = await db.query(
3460
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3461
+ );
3462
+ return encodeDeltaCursor(streamRows2[0]?.cursorId);
3463
+ }
3464
+ const cteSql = `
3465
+ WITH candidate_roots AS (
3466
+ SELECT traceId, spanId, cursorId
3467
+ FROM span_events AS ${outerAlias}
3468
+ ${prefilterWhere}
3469
+ ),
3470
+ root_spans AS (
3471
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3472
+ FROM (
3473
+ ${SPAN_RECONSTRUCT_SELECT}
3474
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3475
+ GROUP BY traceId, spanId
3476
+ ) AS reconstructed
3477
+ INNER JOIN candidate_roots USING (traceId, spanId)
3478
+ )
3479
+ `;
3480
+ const rows = await db.query(
3481
+ `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM root_spans ${postAggWhere}`,
3482
+ [...prefilterParams, ...postAggParams]
3483
+ );
3484
+ const cursorId = rows[0]?.cursorId;
3485
+ if (cursorId !== null && cursorId !== void 0) {
3486
+ return encodeDeltaCursor(cursorId);
3487
+ }
3488
+ const streamRows = await db.query(
3489
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3490
+ );
3491
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3492
+ }
3493
+ async function getTraceStreamHeadCursor(db) {
3494
+ const streamRows = await db.query(
3495
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`
3496
+ );
3497
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3498
+ }
3499
+ async function getBranchDeltaCursor(db, filters) {
3500
+ const filterRecord = filters ?? {};
3501
+ const userSpanType = filterRecord.spanType;
3502
+ const { spanType: _spanType, ...rest } = filterRecord;
3503
+ const { prefilter, postAgg } = partitionAnchorFilters(rest);
3504
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
3505
+ const prefilterParts = [`eventType = 'start'`, `cursorId IS NOT NULL`];
3506
+ let spanTypeParams;
3507
+ if (typeof userSpanType === "string") {
3508
+ prefilterParts.push(`spanType = ?`);
3509
+ spanTypeParams = [userSpanType];
3510
+ } else {
3511
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3512
+ spanTypeParams = [...BRANCH_SPAN_TYPES];
3513
+ }
3514
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3515
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3516
+ const prefilterParams = [...spanTypeParams, ...prefilterFilterParams];
3517
+ const outerAlias = "outer_anchor";
3518
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3519
+ if (!postAggClause) {
3520
+ const rows2 = await db.query(
3521
+ `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`,
3522
+ prefilterParams
3523
+ );
3524
+ const cursorId2 = rows2[0]?.cursorId;
3525
+ if (cursorId2 !== null && cursorId2 !== void 0) {
3526
+ return encodeDeltaCursor(cursorId2);
3527
+ }
3528
+ const streamRows2 = await db.query(
3529
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3530
+ [...BRANCH_SPAN_TYPES]
3531
+ );
3532
+ return encodeDeltaCursor(streamRows2[0]?.cursorId);
3533
+ }
3534
+ const cteSql = `
3535
+ WITH candidate_anchors AS (
3536
+ SELECT traceId, spanId, cursorId
3537
+ FROM span_events AS ${outerAlias}
3538
+ ${prefilterWhere}
3539
+ ),
3540
+ branch_anchors AS (
3541
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3542
+ FROM (
3543
+ ${SPAN_RECONSTRUCT_SELECT}
3544
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3545
+ GROUP BY traceId, spanId
3546
+ ) AS reconstructed
3547
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3548
+ )
3549
+ `;
3550
+ const rows = await db.query(
3551
+ `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM branch_anchors ${postAggClause}`,
3552
+ [...prefilterParams, ...postAggParams]
3553
+ );
3554
+ const cursorId = rows[0]?.cursorId;
3555
+ if (cursorId !== null && cursorId !== void 0) {
3556
+ return encodeDeltaCursor(cursorId);
3557
+ }
3558
+ const streamRows = await db.query(
3559
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3560
+ [...BRANCH_SPAN_TYPES]
3561
+ );
3562
+ return encodeDeltaCursor(streamRows[0]?.cursorId);
3563
+ }
3564
+ async function getBranchStreamHeadCursor(db, userSpanType) {
3565
+ if (userSpanType) {
3566
+ const rows2 = await db.query(
3567
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType = ? AND cursorId IS NOT NULL`,
3568
+ [userSpanType]
3569
+ );
3570
+ return encodeDeltaCursor(rows2[0]?.cursorId);
3571
+ }
3572
+ const rows = await db.query(
3573
+ `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`,
3574
+ [...BRANCH_SPAN_TYPES]
3575
+ );
3576
+ return encodeDeltaCursor(rows[0]?.cursorId);
3577
+ }
2923
3578
 
2924
3579
  // src/storage/domains/observability/index.ts
2925
3580
  function buildSignalMigrationRequiredMessage(args) {
@@ -2968,6 +3623,7 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
2968
3623
  });
2969
3624
  }
2970
3625
  await this.db.executeBatch([...ALL_DDL, ...ALL_MIGRATIONS]);
3626
+ await dropLegacyCursorIdDefaults(this.db);
2971
3627
  }
2972
3628
  /**
2973
3629
  * Manually migrate legacy signal tables to the signal-ID primary-key schema.
@@ -3004,6 +3660,12 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3004
3660
  supported: ["event-sourced"]
3005
3661
  };
3006
3662
  }
3663
+ getFeatures() {
3664
+ if (!deltaPollingFeatureEnabled()) {
3665
+ return void 0;
3666
+ }
3667
+ return ["delta-polling"];
3668
+ }
3007
3669
  // Tracing
3008
3670
  async createSpan(args) {
3009
3671
  return createSpan(this.db, args);
@@ -3140,5 +3802,5 @@ var ObservabilityStorageDuckDB = class extends ObservabilityStorage {
3140
3802
  };
3141
3803
 
3142
3804
  export { ObservabilityStorageDuckDB };
3143
- //# sourceMappingURL=observability-TS5QIHIC.js.map
3144
- //# sourceMappingURL=observability-TS5QIHIC.js.map
3805
+ //# sourceMappingURL=observability-M35AJUGT.js.map
3806
+ //# sourceMappingURL=observability-M35AJUGT.js.map