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