@iann29/rastro 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (125) hide show
  1. package/README.md +82 -29
  2. package/agent/integration.md +17 -15
  3. package/agent/manifest.json +7 -5
  4. package/agent/manifest.schema.json +8 -5
  5. package/dist/client/federation.d.ts +34 -8
  6. package/dist/client/federation.d.ts.map +1 -1
  7. package/dist/client/federation.js +17 -3
  8. package/dist/client/federation.js.map +1 -1
  9. package/dist/client/index.d.ts +418 -21
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +191 -41
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +2 -0
  14. package/dist/component/_generated/api.d.ts.map +1 -1
  15. package/dist/component/_generated/api.js.map +1 -1
  16. package/dist/component/_generated/component.d.ts +63 -7
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/affiliates.d.ts.map +1 -1
  19. package/dist/component/affiliates.js +6 -2
  20. package/dist/component/affiliates.js.map +1 -1
  21. package/dist/component/cardinality.d.ts.map +1 -1
  22. package/dist/component/cardinality.js.map +1 -1
  23. package/dist/component/constants.d.ts +7 -1
  24. package/dist/component/constants.d.ts.map +1 -1
  25. package/dist/component/constants.js +13 -1
  26. package/dist/component/constants.js.map +1 -1
  27. package/dist/component/diagnostics.d.ts.map +1 -1
  28. package/dist/component/diagnostics.js.map +1 -1
  29. package/dist/component/eventStore.d.ts +2 -1
  30. package/dist/component/eventStore.d.ts.map +1 -1
  31. package/dist/component/eventStore.js +13 -10
  32. package/dist/component/eventStore.js.map +1 -1
  33. package/dist/component/funnels.d.ts.map +1 -1
  34. package/dist/component/funnels.js +5 -3
  35. package/dist/component/funnels.js.map +1 -1
  36. package/dist/component/geo.d.ts.map +1 -1
  37. package/dist/component/geo.js +3 -2
  38. package/dist/component/geo.js.map +1 -1
  39. package/dist/component/goals.d.ts.map +1 -1
  40. package/dist/component/goals.js +8 -5
  41. package/dist/component/goals.js.map +1 -1
  42. package/dist/component/guards.d.ts.map +1 -1
  43. package/dist/component/guards.js.map +1 -1
  44. package/dist/component/http.d.ts.map +1 -1
  45. package/dist/component/http.js +51 -17
  46. package/dist/component/http.js.map +1 -1
  47. package/dist/component/ingest.d.ts +2 -1
  48. package/dist/component/ingest.d.ts.map +1 -1
  49. package/dist/component/ingest.js +143 -23
  50. package/dist/component/ingest.js.map +1 -1
  51. package/dist/component/live.d.ts.map +1 -1
  52. package/dist/component/live.js +1 -3
  53. package/dist/component/live.js.map +1 -1
  54. package/dist/component/reports.d.ts +60 -3
  55. package/dist/component/reports.d.ts.map +1 -1
  56. package/dist/component/reports.js +215 -49
  57. package/dist/component/reports.js.map +1 -1
  58. package/dist/component/retention.d.ts +4 -4
  59. package/dist/component/retention.d.ts.map +1 -1
  60. package/dist/component/retention.js +54 -22
  61. package/dist/component/retention.js.map +1 -1
  62. package/dist/component/sanitize.d.ts +11 -0
  63. package/dist/component/sanitize.d.ts.map +1 -1
  64. package/dist/component/sanitize.js +58 -11
  65. package/dist/component/sanitize.js.map +1 -1
  66. package/dist/component/schema.d.ts +47 -5
  67. package/dist/component/schema.js +21 -1
  68. package/dist/component/schema.js.map +1 -1
  69. package/dist/component/sites.d.ts.map +1 -1
  70. package/dist/component/sites.js +6 -6
  71. package/dist/component/sites.js.map +1 -1
  72. package/dist/component/validators.d.ts +14 -9
  73. package/dist/component/validators.d.ts.map +1 -1
  74. package/dist/component/validators.js +5 -1
  75. package/dist/component/validators.js.map +1 -1
  76. package/dist/component/visitors.d.ts.map +1 -1
  77. package/dist/component/visitors.js.map +1 -1
  78. package/dist/component/vitals.d.ts +41 -0
  79. package/dist/component/vitals.d.ts.map +1 -0
  80. package/dist/component/vitals.js +115 -0
  81. package/dist/component/vitals.js.map +1 -0
  82. package/dist/react/index.d.ts.map +1 -1
  83. package/dist/react/index.js.map +1 -1
  84. package/dist/tracker/generated.d.ts +8 -4
  85. package/dist/tracker/generated.d.ts.map +1 -1
  86. package/dist/tracker/generated.js +8 -4
  87. package/dist/tracker/generated.js.map +1 -1
  88. package/dist/tracker/tracker.d.ts.map +1 -1
  89. package/dist/tracker/tracker.js +3 -1
  90. package/dist/tracker/tracker.js.map +1 -1
  91. package/dist/tracker/vitals.d.ts +10 -0
  92. package/dist/tracker/vitals.d.ts.map +1 -0
  93. package/dist/tracker/vitals.js +140 -0
  94. package/dist/tracker/vitals.js.map +1 -0
  95. package/dist/tracker.min.js +1 -1
  96. package/dist/vitals.min.js +1 -0
  97. package/docs/benchmarks/2026-08-20-realistic.md +71 -71
  98. package/docs/benchmarks/2026-08-21-formal-certification.md +50 -30
  99. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  100. package/docs/upgrading.md +46 -8
  101. package/llms.txt +5 -1
  102. package/package.json +17 -3
  103. package/scripts/benchmark-ingest.mjs +101 -48
  104. package/src/component/_generated/api.ts +2 -0
  105. package/src/component/_generated/component.ts +69 -4
  106. package/src/component/affiliates.ts +20 -5
  107. package/src/component/cardinality.ts +8 -7
  108. package/src/component/constants.ts +13 -1
  109. package/src/component/diagnostics.ts +3 -2
  110. package/src/component/eventStore.ts +53 -46
  111. package/src/component/funnels.ts +19 -16
  112. package/src/component/geo.ts +16 -14
  113. package/src/component/goals.ts +29 -21
  114. package/src/component/guards.ts +3 -1
  115. package/src/component/http.ts +93 -54
  116. package/src/component/ingest.ts +320 -127
  117. package/src/component/live.ts +5 -4
  118. package/src/component/reports.ts +563 -219
  119. package/src/component/retention.ts +221 -98
  120. package/src/component/sanitize.ts +99 -29
  121. package/src/component/schema.ts +31 -5
  122. package/src/component/sites.ts +22 -11
  123. package/src/component/validators.ts +16 -7
  124. package/src/component/visitors.ts +16 -5
  125. package/src/component/vitals.ts +146 -0
@@ -1,13 +1,14 @@
1
1
  import { paginationOptsValidator, paginationResultValidator, } from "convex/server";
2
2
  import { v } from "convex/values";
3
3
  import { query } from "./_generated/server.js";
4
- import { AGGREGATE_SHARDS, DAY_MS, FEATURE_REPORT_BYTE_RESERVE, FEATURE_REPORT_DOCUMENT_RESERVE, HOUR_MS, HOURLY_AGGREGATE_SHARDS, MAX_AFFILIATES_PER_SITE, MAX_FUNNELS_PER_SITE, MAX_FEATURE_ROLLUP_DAYS, MAX_FEATURE_ROLLUP_ROWS, MAX_GOALS_PER_SITE, MAX_JOURNEY_EVENTS, MAX_LIVE_VISITORS, MAX_OVERVIEW_AGGREGATE_ROWS, MAX_REPORT_BUCKETS, MAX_REPORT_RANGE_DAYS, MAX_REPORT_SITES, REPORT_ROLLUP_SHARDS, } from "./constants.js";
4
+ import { AGGREGATE_SHARDS, DAY_MS, FEATURE_REPORT_BYTE_RESERVE, FEATURE_REPORT_DOCUMENT_RESERVE, HOUR_MS, HOURLY_AGGREGATE_SHARDS, MAX_AFFILIATES_PER_SITE, MAX_FUNNELS_PER_SITE, MAX_FEATURE_ROLLUP_DAYS, MAX_FEATURE_ROLLUP_ROWS, MAX_GOALS_PER_SITE, MAX_JOURNEY_EVENTS, MAX_LIVE_VISITORS, MAX_OVERVIEW_AGGREGATE_ROWS, MAX_REPORT_BUCKETS, MAX_REPORT_RANGE_DAYS, MAX_REPORT_SITES, MAX_VITALS_REPORT_ROWS, REPORT_ROLLUP_SHARDS, VITALS_REPORT_PAGE_LIMIT, } from "./constants.js";
5
5
  import { createVisitorSketch, estimateVisitors, unionVisitorSketches, } from "./cardinality.js";
6
6
  import { fail } from "./errors.js";
7
7
  import { compareJourneyEvents, hydrateEventContexts, loadSessionJourneyEvents, loadVisitorJourneyEvents, } from "./eventStore.js";
8
8
  import { isPlainRecord } from "./guards.js";
9
- import { affiliateFieldsValidator, eventFieldsValidator, funnelFieldsValidator, goalFieldsValidator, sessionFieldsValidator, } from "./validators.js";
9
+ import { affiliateFieldsValidator, eventFieldsValidator, funnelFieldsValidator, goalFieldsValidator, sessionFieldsValidator, vitalMetricValidator, } from "./validators.js";
10
10
  import { resolveVisitorIdentities } from "./visitors.js";
11
+ import { mergeVitalHistograms, VITAL_ALL, VITAL_METRICS, vitalHistogramPercentile, vitalRatingCounts, } from "./vitals.js";
11
12
  const sessionDocumentValidator = sessionFieldsValidator.extend({
12
13
  _id: v.id("sessions"),
13
14
  _creationTime: v.number(),
@@ -67,7 +68,9 @@ const metricTotalsValidator = v.object({
67
68
  conversions: v.number(),
68
69
  revenueCents: v.number(),
69
70
  });
70
- const timePointValidator = metricTotalsValidator.extend({ timestamp: v.number() });
71
+ const timePointValidator = metricTotalsValidator.extend({
72
+ timestamp: v.number(),
73
+ });
71
74
  const topItemValidator = v.object({
72
75
  value: v.string(),
73
76
  count: v.number(),
@@ -292,7 +295,9 @@ export const liveVisitors = query({
292
295
  handler: async (ctx, args) => {
293
296
  const siteIds = validateSiteIds(args.siteIds);
294
297
  const limit = args.limit ?? 100;
295
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_LIVE_VISITORS) {
298
+ if (!Number.isSafeInteger(limit) ||
299
+ limit < 1 ||
300
+ limit > MAX_LIVE_VISITORS) {
296
301
  fail("INVALID_ARGUMENT", `limit must be an integer between 1 and ${MAX_LIVE_VISITORS}`);
297
302
  }
298
303
  if (!Number.isFinite(args.now) || args.now < 0) {
@@ -488,22 +493,22 @@ export const visitorJourney = query({
488
493
  validateRange(args.from, args.to, MAX_REPORT_RANGE_DAYS);
489
494
  await assertSourceAvailable(ctx, siteIds, ["events"], args.from);
490
495
  const limit = args.limit ?? 200;
491
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_JOURNEY_EVENTS) {
496
+ if (!Number.isSafeInteger(limit) ||
497
+ limit < 1 ||
498
+ limit > MAX_JOURNEY_EVENTS) {
492
499
  fail("INVALID_ARGUMENT", `limit must be an integer between 1 and ${MAX_JOURNEY_EVENTS}`);
493
500
  }
494
501
  const rows = [];
495
502
  for (const siteId of siteIds) {
496
503
  for (const visitorId of await resolveVisitorIdentities(ctx, siteId, args.visitorId)) {
497
- const cutoff = rows.length >= limit
498
- ? rows[limit - 1].timestamp
499
- : args.to;
500
- rows.push(...await loadVisitorJourneyEvents(ctx, {
504
+ const cutoff = rows.length >= limit ? rows[limit - 1].timestamp : args.to;
505
+ rows.push(...(await loadVisitorJourneyEvents(ctx, {
501
506
  siteId,
502
507
  visitorId,
503
508
  from: args.from,
504
509
  to: cutoff,
505
510
  resultLimit: limit,
506
- }));
511
+ })));
507
512
  rows.sort(compareJourneyEvents);
508
513
  if (rows.length > limit)
509
514
  rows.length = limit;
@@ -586,7 +591,12 @@ export const goalsReport = query({
586
591
  const funnelReportItemValidator = funnelFieldsValidator.extend({
587
592
  _id: v.id("funnels"),
588
593
  _creationTime: v.number(),
589
- stepsReport: v.array(v.object({ step: v.number(), name: v.string(), entrants: v.number(), completions: v.number() })),
594
+ stepsReport: v.array(v.object({
595
+ step: v.number(),
596
+ name: v.string(),
597
+ entrants: v.number(),
598
+ completions: v.number(),
599
+ })),
590
600
  truncated: v.boolean(),
591
601
  metadata: reportMetadataValidator,
592
602
  });
@@ -649,7 +659,9 @@ export const funnelsReport = query({
649
659
  stepsReport,
650
660
  truncated: false,
651
661
  metadata: {
652
- basis: rollups ? "dailyRollup" : "boundedLegacy",
662
+ basis: rollups
663
+ ? "dailyRollup"
664
+ : "boundedLegacy",
653
665
  coverage: reportCoverage,
654
666
  range: rangeSemantics(args.from, args.to),
655
667
  },
@@ -775,7 +787,7 @@ export const affiliatesReport = query({
775
787
  visits: traffic?.visits ?? 0,
776
788
  uniqueVisitors: rollups
777
789
  ? estimateVisitors(traffic?.sketch ?? createVisitorSketch())
778
- : traffic?.visitors.size ?? 0,
790
+ : (traffic?.visitors.size ?? 0),
779
791
  };
780
792
  }),
781
793
  truncated: false,
@@ -790,14 +802,128 @@ export const affiliatesReport = query({
790
802
  }),
791
803
  range: rangeSemantics(args.from, args.to),
792
804
  uniqueVisitors: {
793
- basis: rollups ? "estimatedUnique" : "exactLegacy",
805
+ basis: rollups
806
+ ? "estimatedUnique"
807
+ : "exactLegacy",
794
808
  exact: !rollups,
795
809
  },
796
810
  },
797
811
  };
798
812
  },
799
813
  });
800
- const coverageDatasetValidator = v.union(v.literal("overviewHour"), v.literal("overviewDay"), v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goals"), v.literal("funnels"), v.literal("affiliates"));
814
+ const vitalStatsValidator = v.object({
815
+ count: v.number(),
816
+ p75: v.number(),
817
+ average: v.number(),
818
+ good: v.number(),
819
+ needsImprovement: v.number(),
820
+ poor: v.number(),
821
+ });
822
+ export const vitalsReport = query({
823
+ args: { siteId: v.id("sites"), from: v.number(), to: v.number() },
824
+ returns: v.object({
825
+ metrics: v.array(vitalStatsValidator.extend({
826
+ metric: vitalMetricValidator,
827
+ devices: v.array(vitalStatsValidator.extend({ device: v.string() })),
828
+ pages: v.array(vitalStatsValidator.extend({ page: v.string() })),
829
+ timeSeries: v.array(v.object({
830
+ timestamp: v.number(),
831
+ count: v.number(),
832
+ p75: v.number(),
833
+ })),
834
+ })),
835
+ truncated: v.boolean(),
836
+ metadata: reportMetadataValidator,
837
+ }),
838
+ handler: async (ctx, args) => {
839
+ validateRange(args.from, args.to, MAX_FEATURE_ROLLUP_DAYS);
840
+ requireAlignedRange(args.from, args.to, DAY_MS, "vitals report");
841
+ await assertSourceAvailable(ctx, [args.siteId], ["vitalRollups"], args.from);
842
+ const rows = await ctx.db
843
+ .query("vitalRollups")
844
+ .withIndex("by_siteId_and_bucketStart", (range) => range
845
+ .eq("siteId", args.siteId)
846
+ .gte("bucketStart", args.from)
847
+ .lte("bucketStart", args.to))
848
+ .take(MAX_VITALS_REPORT_ROWS + 1);
849
+ failIfIncomplete(rows, MAX_VITALS_REPORT_ROWS, "vital rollups");
850
+ const fold = (groups, metric, key, entry) => {
851
+ const group = groups.get(metric) ?? new Map();
852
+ const current = group.get(key) ?? { count: 0, sum: 0, histogram: [] };
853
+ current.count += entry.count;
854
+ current.sum += entry.sum;
855
+ current.histogram = mergeVitalHistograms(current.histogram, entry.histogram);
856
+ group.set(key, current);
857
+ groups.set(metric, group);
858
+ };
859
+ const byDevice = new Map();
860
+ const byPage = new Map();
861
+ const byDay = new Map();
862
+ for (const row of rows) {
863
+ for (const entry of row.metrics) {
864
+ if (row.page === VITAL_ALL) {
865
+ // A device row carries the complete signal once, so overall totals
866
+ // and the time series derive from this family alone.
867
+ fold(byDevice, entry.metric, row.device, entry);
868
+ fold(byDay, entry.metric, row.bucketStart, entry);
869
+ }
870
+ else {
871
+ fold(byPage, entry.metric, row.page, entry);
872
+ }
873
+ }
874
+ }
875
+ const stats = (metric, accumulated) => ({
876
+ count: accumulated.count,
877
+ p75: vitalHistogramPercentile(accumulated.histogram, 0.75),
878
+ average: accumulated.count > 0
879
+ ? Math.round(accumulated.sum / accumulated.count)
880
+ : 0,
881
+ ...vitalRatingCounts(accumulated.histogram, metric),
882
+ });
883
+ const metrics = VITAL_METRICS.map((metric) => {
884
+ const overall = { count: 0, sum: 0, histogram: [] };
885
+ for (const accumulated of byDevice.get(metric)?.values() ?? []) {
886
+ overall.count += accumulated.count;
887
+ overall.sum += accumulated.sum;
888
+ overall.histogram = mergeVitalHistograms(overall.histogram, accumulated.histogram);
889
+ }
890
+ return {
891
+ metric,
892
+ ...stats(metric, overall),
893
+ devices: [...(byDevice.get(metric) ?? [])]
894
+ .map(([device, accumulated]) => ({
895
+ device,
896
+ ...stats(metric, accumulated),
897
+ }))
898
+ .sort((left, right) => right.count - left.count),
899
+ pages: [...(byPage.get(metric) ?? [])]
900
+ .map(([page, accumulated]) => ({
901
+ page,
902
+ ...stats(metric, accumulated),
903
+ }))
904
+ .sort((left, right) => right.count - left.count || left.page.localeCompare(right.page))
905
+ .slice(0, VITALS_REPORT_PAGE_LIMIT),
906
+ timeSeries: [...(byDay.get(metric) ?? [])]
907
+ .sort(([left], [right]) => left - right)
908
+ .map(([timestamp, accumulated]) => ({
909
+ timestamp,
910
+ count: accumulated.count,
911
+ p75: vitalHistogramPercentile(accumulated.histogram, 0.75),
912
+ })),
913
+ };
914
+ });
915
+ return {
916
+ metrics,
917
+ truncated: false,
918
+ metadata: {
919
+ basis: "dailyRollup",
920
+ coverage: await coverageForRange(ctx, [args.siteId], args.from, args.to, { datasets: ["vitalRollups"], complete: true }),
921
+ range: rangeSemantics(args.from, args.to),
922
+ },
923
+ };
924
+ },
925
+ });
926
+ const coverageDatasetValidator = v.union(v.literal("overviewHour"), v.literal("overviewDay"), v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goals"), v.literal("funnels"), v.literal("affiliates"), v.literal("vitals"));
801
927
  export const dataCoverage = query({
802
928
  args: {
803
929
  siteId: v.id("sites"),
@@ -824,6 +950,7 @@ export const dataCoverage = query({
824
950
  "goals",
825
951
  "funnels",
826
952
  "affiliates",
953
+ "vitals",
827
954
  ];
828
955
  return {
829
956
  siteId: args.siteId,
@@ -967,10 +1094,11 @@ async function coverageForRange(ctx, siteIds, from, to, options) {
967
1094
  else if (retainedBefore !== null && from < retainedBefore) {
968
1095
  state = "partial";
969
1096
  }
970
- else if (options.complete || (availableFrom !== null &&
971
- availableThrough !== null &&
972
- from >= availableFrom &&
973
- to <= availableThrough)) {
1097
+ else if (options.complete ||
1098
+ (availableFrom !== null &&
1099
+ availableThrough !== null &&
1100
+ from >= availableFrom &&
1101
+ to <= availableThrough)) {
974
1102
  state = "complete";
975
1103
  }
976
1104
  else if (sourceRanges.some(Boolean)) {
@@ -1007,6 +1135,14 @@ async function assertSourceAvailable(ctx, siteIds, sources, from) {
1007
1135
  }
1008
1136
  }
1009
1137
  async function datasetCoverageForRange(ctx, siteId, dataset, from, to) {
1138
+ // Vitals have no legacy fallback source; the rollup either covers the range
1139
+ // or the coverage states say how much of it exists.
1140
+ if (dataset === "vitals") {
1141
+ return coverageForRange(ctx, [siteId], from, to, {
1142
+ datasets: ["vitalRollups"],
1143
+ complete: false,
1144
+ });
1145
+ }
1010
1146
  const sourceOptions = dataset === "goals"
1011
1147
  ? [["goalRollups"], ["goalCompletions"]]
1012
1148
  : dataset === "funnels"
@@ -1043,54 +1179,76 @@ async function sourceRange(ctx, siteId, source) {
1043
1179
  if (source === "overviewHour" || source === "overviewDay") {
1044
1180
  const granularity = source === "overviewHour" ? "hour" : "day";
1045
1181
  const [first, last] = await Promise.all([
1046
- ctx.db.query("aggregateBuckets")
1047
- .withIndex("by_siteId_and_granularity_and_bucketStart", (range) => range.eq("siteId", siteId).eq("granularity", granularity)).order("asc").take(1),
1048
- ctx.db.query("aggregateBuckets")
1049
- .withIndex("by_siteId_and_granularity_and_bucketStart", (range) => range.eq("siteId", siteId).eq("granularity", granularity)).order("desc").take(1),
1182
+ ctx.db
1183
+ .query("aggregateBuckets")
1184
+ .withIndex("by_siteId_and_granularity_and_bucketStart", (range) => range.eq("siteId", siteId).eq("granularity", granularity))
1185
+ .order("asc")
1186
+ .take(1),
1187
+ ctx.db
1188
+ .query("aggregateBuckets")
1189
+ .withIndex("by_siteId_and_granularity_and_bucketStart", (range) => range.eq("siteId", siteId).eq("granularity", granularity))
1190
+ .order("desc")
1191
+ .take(1),
1050
1192
  ]);
1051
1193
  return bounds(first[0]?.bucketStart, last[0]?.bucketStart);
1052
1194
  }
1053
1195
  if (source === "events") {
1054
1196
  const [first, last] = await Promise.all([
1055
- ctx.db.query("eventBatches")
1197
+ ctx.db
1198
+ .query("eventBatches")
1056
1199
  .withIndex("by_siteId_and_bucketStart", (range) => range.eq("siteId", siteId))
1057
- .order("asc").take(1),
1058
- ctx.db.query("eventBatches")
1200
+ .order("asc")
1201
+ .take(1),
1202
+ ctx.db
1203
+ .query("eventBatches")
1059
1204
  .withIndex("by_siteId_and_bucketStart", (range) => range.eq("siteId", siteId))
1060
- .order("desc").take(1),
1205
+ .order("desc")
1206
+ .take(1),
1061
1207
  ]);
1062
1208
  return bounds(first[0]?.bucketStart, last[0]?.bucketStart);
1063
1209
  }
1064
1210
  if (source === "sessions") {
1065
1211
  const [first, last] = await Promise.all([
1066
- ctx.db.query("sessions")
1212
+ ctx.db
1213
+ .query("sessions")
1067
1214
  .withIndex("by_siteId_and_startedAt", (range) => range.eq("siteId", siteId))
1068
- .order("asc").take(1),
1069
- ctx.db.query("sessions")
1215
+ .order("asc")
1216
+ .take(1),
1217
+ ctx.db
1218
+ .query("sessions")
1070
1219
  .withIndex("by_siteId_and_lastSeenAt", (range) => range.eq("siteId", siteId))
1071
- .order("desc").take(1),
1220
+ .order("desc")
1221
+ .take(1),
1072
1222
  ]);
1073
1223
  return bounds(first[0]?.startedAt, last[0]?.lastSeenAt);
1074
1224
  }
1075
1225
  if (source === "conversions") {
1076
1226
  const [first, last] = await Promise.all([
1077
- ctx.db.query("conversions")
1227
+ ctx.db
1228
+ .query("conversions")
1078
1229
  .withIndex("by_siteId_and_timestamp", (range) => range.eq("siteId", siteId))
1079
- .order("asc").take(1),
1080
- ctx.db.query("conversions")
1230
+ .order("asc")
1231
+ .take(1),
1232
+ ctx.db
1233
+ .query("conversions")
1081
1234
  .withIndex("by_siteId_and_timestamp", (range) => range.eq("siteId", siteId))
1082
- .order("desc").take(1),
1235
+ .order("desc")
1236
+ .take(1),
1083
1237
  ]);
1084
1238
  return bounds(first[0]?.timestamp, last[0]?.timestamp);
1085
1239
  }
1086
1240
  if (source === "goalCompletions") {
1087
1241
  const [first, last] = await Promise.all([
1088
- ctx.db.query("goalCompletions")
1242
+ ctx.db
1243
+ .query("goalCompletions")
1089
1244
  .withIndex("by_siteId_and_timestamp", (range) => range.eq("siteId", siteId))
1090
- .order("asc").take(1),
1091
- ctx.db.query("goalCompletions")
1245
+ .order("asc")
1246
+ .take(1),
1247
+ ctx.db
1248
+ .query("goalCompletions")
1092
1249
  .withIndex("by_siteId_and_timestamp", (range) => range.eq("siteId", siteId))
1093
- .order("desc").take(1),
1250
+ .order("desc")
1251
+ .take(1),
1094
1252
  ]);
1095
1253
  return bounds(first[0]?.timestamp, last[0]?.timestamp);
1096
1254
  }
@@ -1102,14 +1260,20 @@ async function sourceRange(ctx, siteId, source) {
1102
1260
  ? "goalDailyRollups"
1103
1261
  : source === "funnelRollups"
1104
1262
  ? "funnelDailyRollups"
1105
- : "affiliateDailyRollups";
1263
+ : source === "vitalRollups"
1264
+ ? "vitalRollups"
1265
+ : "affiliateDailyRollups";
1106
1266
  const [first, last] = await Promise.all([
1107
- ctx.db.query(table)
1267
+ ctx.db
1268
+ .query(table)
1108
1269
  .withIndex("by_siteId_and_bucketStart", (range) => range.eq("siteId", siteId))
1109
- .order("asc").take(1),
1110
- ctx.db.query(table)
1270
+ .order("asc")
1271
+ .take(1),
1272
+ ctx.db
1273
+ .query(table)
1111
1274
  .withIndex("by_siteId_and_bucketStart", (range) => range.eq("siteId", siteId))
1112
- .order("desc").take(1),
1275
+ .order("desc")
1276
+ .take(1),
1113
1277
  ]);
1114
1278
  return bounds(first[0]?.bucketStart, last[0]?.bucketStart);
1115
1279
  }
@@ -1151,7 +1315,9 @@ function addMetrics(target, source) {
1151
1315
  }
1152
1316
  function validateSiteIds(siteIds) {
1153
1317
  const unique = [...new Set(siteIds)];
1154
- if (unique.length < 1 || unique.length > MAX_REPORT_SITES || unique.length !== siteIds.length) {
1318
+ if (unique.length < 1 ||
1319
+ unique.length > MAX_REPORT_SITES ||
1320
+ unique.length !== siteIds.length) {
1155
1321
  fail("INVALID_ARGUMENT", `siteIds must contain 1-${MAX_REPORT_SITES} unique IDs`);
1156
1322
  }
1157
1323
  return unique;
@@ -1262,9 +1428,7 @@ function parseJourneyPagination(options, scope) {
1262
1428
  function keysetPaginationResult(rows, pagination, cursorFor) {
1263
1429
  const page = rows.slice(0, pagination.numItems);
1264
1430
  const isDone = rows.length <= pagination.numItems;
1265
- const next = page.length > 0
1266
- ? cursorFor(page[page.length - 1])
1267
- : pagination.cursor;
1431
+ const next = page.length > 0 ? cursorFor(page[page.length - 1]) : pagination.cursor;
1268
1432
  return {
1269
1433
  page,
1270
1434
  isDone,
@@ -1308,7 +1472,9 @@ function journeyPaginationResult(rows, pagination) {
1308
1472
  };
1309
1473
  }
1310
1474
  function validatePagination(options) {
1311
- if (!Number.isSafeInteger(options.numItems) || options.numItems < 1 || options.numItems > 100) {
1475
+ if (!Number.isSafeInteger(options.numItems) ||
1476
+ options.numItems < 1 ||
1477
+ options.numItems > 100) {
1312
1478
  fail("INVALID_ARGUMENT", "pagination numItems must be an integer between 1 and 100");
1313
1479
  }
1314
1480
  if (options.maximumRowsRead !== undefined &&