@iann29/rastro 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (179) hide show
  1. package/README.md +693 -69
  2. package/agent/integration.md +801 -0
  3. package/agent/manifest.json +205 -0
  4. package/agent/manifest.schema.json +444 -0
  5. package/dist/client/federation.d.ts +381 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +274 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +2673 -15
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +565 -33
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +22 -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 +315 -4
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/_generated/server.d.ts +4 -0
  19. package/dist/component/_generated/server.d.ts.map +1 -1
  20. package/dist/component/_generated/server.js.map +1 -1
  21. package/dist/component/affiliates.d.ts.map +1 -1
  22. package/dist/component/affiliates.js +6 -2
  23. package/dist/component/affiliates.js.map +1 -1
  24. package/dist/component/cardinality.d.ts +12 -0
  25. package/dist/component/cardinality.d.ts.map +1 -0
  26. package/dist/component/cardinality.js +94 -0
  27. package/dist/component/cardinality.js.map +1 -0
  28. package/dist/component/constants.d.ts +28 -1
  29. package/dist/component/constants.d.ts.map +1 -1
  30. package/dist/component/constants.js +44 -1
  31. package/dist/component/constants.js.map +1 -1
  32. package/dist/component/convex.config.d.ts +6 -1
  33. package/dist/component/convex.config.js +9 -1
  34. package/dist/component/convex.config.js.map +1 -1
  35. package/dist/component/coverage.d.ts +27 -0
  36. package/dist/component/coverage.d.ts.map +1 -0
  37. package/dist/component/coverage.js +56 -0
  38. package/dist/component/coverage.js.map +1 -0
  39. package/dist/component/diagnostics.d.ts +9 -0
  40. package/dist/component/diagnostics.d.ts.map +1 -0
  41. package/dist/component/diagnostics.js +47 -0
  42. package/dist/component/diagnostics.js.map +1 -0
  43. package/dist/component/errors.d.ts +1 -1
  44. package/dist/component/errors.d.ts.map +1 -1
  45. package/dist/component/errors.js.map +1 -1
  46. package/dist/component/eventStore.d.ts +21 -9
  47. package/dist/component/eventStore.d.ts.map +1 -1
  48. package/dist/component/eventStore.js +142 -152
  49. package/dist/component/eventStore.js.map +1 -1
  50. package/dist/component/funnels.d.ts.map +1 -1
  51. package/dist/component/funnels.js +5 -3
  52. package/dist/component/funnels.js.map +1 -1
  53. package/dist/component/geo.d.ts +73 -0
  54. package/dist/component/geo.d.ts.map +1 -0
  55. package/dist/component/geo.js +648 -0
  56. package/dist/component/geo.js.map +1 -0
  57. package/dist/component/goals.d.ts.map +1 -1
  58. package/dist/component/goals.js +8 -5
  59. package/dist/component/goals.js.map +1 -1
  60. package/dist/component/guards.d.ts.map +1 -1
  61. package/dist/component/guards.js.map +1 -1
  62. package/dist/component/http.d.ts.map +1 -1
  63. package/dist/component/http.js +281 -62
  64. package/dist/component/http.js.map +1 -1
  65. package/dist/component/identity.d.ts +13 -0
  66. package/dist/component/identity.d.ts.map +1 -0
  67. package/dist/component/identity.js +58 -0
  68. package/dist/component/identity.js.map +1 -0
  69. package/dist/component/ingest.d.ts +3 -1
  70. package/dist/component/ingest.d.ts.map +1 -1
  71. package/dist/component/ingest.js +498 -95
  72. package/dist/component/ingest.js.map +1 -1
  73. package/dist/component/live.d.ts.map +1 -1
  74. package/dist/component/live.js +7 -6
  75. package/dist/component/live.js.map +1 -1
  76. package/dist/component/localTime.d.ts +25 -0
  77. package/dist/component/localTime.d.ts.map +1 -0
  78. package/dist/component/localTime.js +126 -0
  79. package/dist/component/localTime.js.map +1 -0
  80. package/dist/component/reports.d.ts +266 -10
  81. package/dist/component/reports.d.ts.map +1 -1
  82. package/dist/component/reports.js +1243 -137
  83. package/dist/component/reports.js.map +1 -1
  84. package/dist/component/retention.d.ts +75 -1
  85. package/dist/component/retention.d.ts.map +1 -1
  86. package/dist/component/retention.js +561 -38
  87. package/dist/component/retention.js.map +1 -1
  88. package/dist/component/rollupStore.d.ts +320 -0
  89. package/dist/component/rollupStore.d.ts.map +1 -0
  90. package/dist/component/rollupStore.js +596 -0
  91. package/dist/component/rollupStore.js.map +1 -0
  92. package/dist/component/rollups.d.ts +20 -0
  93. package/dist/component/rollups.d.ts.map +1 -0
  94. package/dist/component/rollups.js +73 -0
  95. package/dist/component/rollups.js.map +1 -0
  96. package/dist/component/sanitize.d.ts +24 -1
  97. package/dist/component/sanitize.d.ts.map +1 -1
  98. package/dist/component/sanitize.js +96 -16
  99. package/dist/component/sanitize.js.map +1 -1
  100. package/dist/component/schema.d.ts +687 -65
  101. package/dist/component/schema.js +187 -20
  102. package/dist/component/schema.js.map +1 -1
  103. package/dist/component/sites.d.ts +12 -0
  104. package/dist/component/sites.d.ts.map +1 -1
  105. package/dist/component/sites.js +41 -7
  106. package/dist/component/sites.js.map +1 -1
  107. package/dist/component/useragent.d.ts +9 -0
  108. package/dist/component/useragent.d.ts.map +1 -0
  109. package/dist/component/useragent.js +152 -0
  110. package/dist/component/useragent.js.map +1 -0
  111. package/dist/component/validators.d.ts +124 -69
  112. package/dist/component/validators.d.ts.map +1 -1
  113. package/dist/component/validators.js +35 -14
  114. package/dist/component/validators.js.map +1 -1
  115. package/dist/component/visitors.d.ts +19 -0
  116. package/dist/component/visitors.d.ts.map +1 -0
  117. package/dist/component/visitors.js +86 -0
  118. package/dist/component/visitors.js.map +1 -0
  119. package/dist/component/vitals.d.ts +41 -0
  120. package/dist/component/vitals.d.ts.map +1 -0
  121. package/dist/component/vitals.js +115 -0
  122. package/dist/component/vitals.js.map +1 -0
  123. package/dist/react/index.d.ts.map +1 -1
  124. package/dist/react/index.js.map +1 -1
  125. package/dist/tracker/generated.d.ts +11 -4
  126. package/dist/tracker/generated.d.ts.map +1 -1
  127. package/dist/tracker/generated.js +11 -4
  128. package/dist/tracker/generated.js.map +1 -1
  129. package/dist/tracker/tracker.d.ts +1 -1
  130. package/dist/tracker/tracker.d.ts.map +1 -1
  131. package/dist/tracker/tracker.js +57 -20
  132. package/dist/tracker/tracker.js.map +1 -1
  133. package/dist/tracker/vitals.d.ts +10 -0
  134. package/dist/tracker/vitals.d.ts.map +1 -0
  135. package/dist/tracker/vitals.js +140 -0
  136. package/dist/tracker/vitals.js.map +1 -0
  137. package/dist/tracker.min.js +1 -1
  138. package/dist/vitals.min.js +1 -0
  139. package/docs/benchmarks/2026-08-20-realistic.md +76 -76
  140. package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
  141. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  142. package/docs/federation-setup.md +464 -0
  143. package/docs/federation.md +352 -0
  144. package/docs/upgrading.md +344 -0
  145. package/llms.txt +72 -0
  146. package/package.json +55 -11
  147. package/scripts/benchmark-ingest.mjs +175 -73
  148. package/scripts/generate-federation-keys.mjs +20 -0
  149. package/src/component/_generated/api.ts +22 -0
  150. package/src/component/_generated/component.ts +381 -4
  151. package/src/component/_generated/server.ts +4 -0
  152. package/src/component/affiliates.ts +20 -5
  153. package/src/component/cardinality.ts +117 -0
  154. package/src/component/constants.ts +44 -1
  155. package/src/component/convex.config.ts +11 -1
  156. package/src/component/coverage.ts +71 -0
  157. package/src/component/diagnostics.ts +65 -0
  158. package/src/component/errors.ts +2 -1
  159. package/src/component/eventStore.ts +217 -193
  160. package/src/component/funnels.ts +19 -16
  161. package/src/component/geo.ts +835 -0
  162. package/src/component/goals.ts +29 -21
  163. package/src/component/guards.ts +3 -1
  164. package/src/component/http.ts +404 -70
  165. package/src/component/identity.ts +74 -0
  166. package/src/component/ingest.ts +894 -188
  167. package/src/component/live.ts +13 -7
  168. package/src/component/localTime.ts +167 -0
  169. package/src/component/reports.ts +1872 -197
  170. package/src/component/retention.ts +788 -96
  171. package/src/component/rollupStore.ts +799 -0
  172. package/src/component/rollups.ts +82 -0
  173. package/src/component/sanitize.ts +144 -29
  174. package/src/component/schema.ts +217 -21
  175. package/src/component/sites.ts +59 -12
  176. package/src/component/useragent.ts +171 -0
  177. package/src/component/validators.ts +49 -14
  178. package/src/component/visitors.ts +116 -0
  179. package/src/component/vitals.ts +146 -0
@@ -65,6 +65,19 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
65
65
  Name
66
66
  >;
67
67
  };
68
+ diagnostics: {
69
+ sessionEventTotals: FunctionReference<
70
+ "query",
71
+ "internal",
72
+ { sessionIds: Array<string>; siteId: string },
73
+ {
74
+ foundSessions: number;
75
+ requestedSessions: number;
76
+ storedEvents: number;
77
+ },
78
+ Name
79
+ >;
80
+ };
68
81
  funnels: {
69
82
  list: FunctionReference<
70
83
  "query",
@@ -172,6 +185,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
172
185
  latitude?: number;
173
186
  longitude?: number;
174
187
  os?: string;
188
+ visitorKey?: string;
175
189
  };
176
190
  events: Array<{
177
191
  affiliateSlug?: string;
@@ -193,7 +207,10 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
193
207
  | "custom"
194
208
  | "conversion"
195
209
  | "heartbeat"
196
- | "outbound";
210
+ | "leave"
211
+ | "outbound"
212
+ | "vital";
213
+ value?: number;
197
214
  visitorId: string;
198
215
  }>;
199
216
  origin: string;
@@ -244,10 +261,62 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
244
261
  updatedAt: number;
245
262
  visits: number;
246
263
  }>;
264
+ metadata: {
265
+ basis: "dailyRollup" | "boundedLegacy";
266
+ coverage: {
267
+ availableFrom: number | null;
268
+ availableThrough: number | null;
269
+ generation: number | null;
270
+ retainedBefore: number | null;
271
+ state: "complete" | "partial" | "retained" | "unavailable";
272
+ };
273
+ range: {
274
+ boundary: "inclusive";
275
+ from: number;
276
+ timezone: string;
277
+ to: number;
278
+ };
279
+ uniqueVisitors: {
280
+ basis: "estimatedUnique" | "exactLegacy";
281
+ exact: boolean;
282
+ };
283
+ };
247
284
  truncated: boolean;
248
285
  },
249
286
  Name
250
287
  >;
288
+ dataCoverage: FunctionReference<
289
+ "query",
290
+ "internal",
291
+ { from: number; siteId: string; to: number },
292
+ {
293
+ datasets: Array<{
294
+ coverage: {
295
+ availableFrom: number | null;
296
+ availableThrough: number | null;
297
+ generation: number | null;
298
+ retainedBefore: number | null;
299
+ state: "complete" | "partial" | "retained" | "unavailable";
300
+ };
301
+ dataset:
302
+ | "overviewHour"
303
+ | "overviewDay"
304
+ | "overviewLocalDay"
305
+ | "events"
306
+ | "sessions"
307
+ | "conversions"
308
+ | "goals"
309
+ | "funnels"
310
+ | "affiliates"
311
+ | "vitals"
312
+ | "siteMap";
313
+ }>;
314
+ from: number;
315
+ siteId: string;
316
+ to: number;
317
+ },
318
+ Name
319
+ >;
251
320
  funnelsReport: FunctionReference<
252
321
  "query",
253
322
  "internal",
@@ -259,6 +328,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
259
328
  conversionWindowMs: number;
260
329
  createdAt: number;
261
330
  key: string;
331
+ metadata: {
332
+ basis: "dailyRollup" | "boundedLegacy";
333
+ coverage: {
334
+ availableFrom: number | null;
335
+ availableThrough: number | null;
336
+ generation: number | null;
337
+ retainedBefore: number | null;
338
+ state: "complete" | "partial" | "retained" | "unavailable";
339
+ };
340
+ range: {
341
+ boundary: "inclusive";
342
+ from: number;
343
+ timezone: string;
344
+ to: number;
345
+ };
346
+ };
262
347
  name: string;
263
348
  siteId: string;
264
349
  steps: Array<{
@@ -296,6 +381,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
296
381
  updatedAt: number;
297
382
  valueCents: number;
298
383
  }>;
384
+ metadata: {
385
+ basis: "dailyRollup" | "boundedLegacy";
386
+ coverage: {
387
+ availableFrom: number | null;
388
+ availableThrough: number | null;
389
+ generation: number | null;
390
+ retainedBefore: number | null;
391
+ state: "complete" | "partial" | "retained" | "unavailable";
392
+ };
393
+ range: {
394
+ boundary: "inclusive";
395
+ from: number;
396
+ timezone: string;
397
+ to: number;
398
+ };
399
+ };
299
400
  truncated: boolean;
300
401
  },
301
402
  Name
@@ -394,6 +495,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
394
495
  utmMedium?: string;
395
496
  utmSource?: string;
396
497
  visitorId: string;
498
+ visitorKey?: string;
397
499
  }>;
398
500
  pageStatus?: "SplitRecommended" | "SplitRequired" | null;
399
501
  splitCursor?: string | null;
@@ -416,9 +518,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
416
518
  lastSeenAt: number;
417
519
  latitude?: number;
418
520
  longitude?: number;
521
+ movedAt?: number;
419
522
  os: string;
420
523
  pageviewCount: number;
421
524
  path: string;
525
+ previousPath?: string;
422
526
  sessionId: string;
423
527
  siteId: string;
424
528
  source: string;
@@ -440,11 +544,53 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
440
544
  currency: string;
441
545
  from: number;
442
546
  interval: "hour" | "day";
547
+ metadata: {
548
+ breakdowns: {
549
+ topBrowsers: "pageviews" | "sessions" | "eventVolume";
550
+ topCampaigns: "pageviews" | "sessions" | "eventVolume";
551
+ topCountries: "pageviews" | "sessions" | "eventVolume";
552
+ topDevices: "pageviews" | "sessions" | "eventVolume";
553
+ topEvents: "eventVolume";
554
+ topGoals: "goalCompletionVolume";
555
+ topPages: "pageviews" | "sessions" | "eventVolume";
556
+ topSources: "pageviews" | "sessions" | "eventVolume";
557
+ };
558
+ coverage: {
559
+ availableFrom: number | null;
560
+ availableThrough: number | null;
561
+ generation: number | null;
562
+ retainedBefore: number | null;
563
+ state: "complete" | "partial" | "retained" | "unavailable";
564
+ };
565
+ engagement: "counted" | "unavailable";
566
+ features: {
567
+ botDetection: "userAgent";
568
+ errorInsights: "unsupported";
569
+ outboundLinks: "eventVolume";
570
+ sessionReplay: "unsupported";
571
+ };
572
+ range: {
573
+ boundary: "inclusive";
574
+ from: number;
575
+ timezone: string;
576
+ to: number;
577
+ };
578
+ visitors: {
579
+ basis: "estimatedUnique" | "bucketOccurrences";
580
+ exact: boolean;
581
+ legacyIncomplete: boolean;
582
+ precision: number | null;
583
+ sketchVersion: number | null;
584
+ };
585
+ };
443
586
  timeSeries: Array<{
444
587
  clicks: number;
588
+ continuedSessions: number;
445
589
  conversions: number;
446
590
  customEvents: number;
591
+ durationMs: number;
447
592
  events: number;
593
+ newVisitors: number;
448
594
  outboundClicks: number;
449
595
  pageviews: number;
450
596
  revenueCents: number;
@@ -458,6 +604,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
458
604
  revenueCents: number;
459
605
  value: string;
460
606
  }>;
607
+ topCampaigns: Array<{
608
+ count: number;
609
+ revenueCents: number;
610
+ value: string;
611
+ }>;
461
612
  topCountries: Array<{
462
613
  count: number;
463
614
  revenueCents: number;
@@ -490,9 +641,12 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
490
641
  }>;
491
642
  totals: {
492
643
  clicks: number;
644
+ continuedSessions: number;
493
645
  conversions: number;
494
646
  customEvents: number;
647
+ durationMs: number;
495
648
  events: number;
649
+ newVisitors: number;
496
650
  outboundClicks: number;
497
651
  pageviews: number;
498
652
  revenueCents: number;
@@ -552,7 +706,9 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
552
706
  | "custom"
553
707
  | "conversion"
554
708
  | "heartbeat"
555
- | "outbound";
709
+ | "leave"
710
+ | "outbound"
711
+ | "vital";
556
712
  visitorId: string;
557
713
  }>;
558
714
  pageStatus?: "SplitRecommended" | "SplitRequired" | null;
@@ -560,6 +716,54 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
560
716
  },
561
717
  Name
562
718
  >;
719
+ siteMap: FunctionReference<
720
+ "query",
721
+ "internal",
722
+ { from: number; siteIds: Array<string>; to: number },
723
+ {
724
+ from: number;
725
+ metadata: {
726
+ basis: "dailyRollup" | "boundedLegacy";
727
+ coverage: {
728
+ availableFrom: number | null;
729
+ availableThrough: number | null;
730
+ generation: number | null;
731
+ retainedBefore: number | null;
732
+ state: "complete" | "partial" | "retained" | "unavailable";
733
+ };
734
+ limits: {
735
+ routesPerDay: number;
736
+ rows: number;
737
+ transitionSlots: number;
738
+ };
739
+ range: {
740
+ boundary: "inclusive";
741
+ from: number;
742
+ timezone: string;
743
+ to: number;
744
+ };
745
+ visitors: { basis: "estimatedUnique"; exact: boolean };
746
+ };
747
+ routes: Array<{
748
+ bounces: number;
749
+ daily: Array<{ bucketStart: number; pageviews: number }>;
750
+ dwellMs: number;
751
+ dwellSamples: number;
752
+ entries: number;
753
+ exits: number;
754
+ pageviews: number;
755
+ route: string;
756
+ transitionsIn: number;
757
+ transitionsOut: number;
758
+ visitors: number;
759
+ }>;
760
+ to: number;
761
+ transitions: Array<{ count: number; from: string; to: string }>;
762
+ truncated: { routes: boolean; transitions: boolean };
763
+ window: { from: number; to: number } | null;
764
+ },
765
+ Name
766
+ >;
563
767
  visitorJourney: FunctionReference<
564
768
  "query",
565
769
  "internal",
@@ -600,11 +804,70 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
600
804
  | "custom"
601
805
  | "conversion"
602
806
  | "heartbeat"
603
- | "outbound";
807
+ | "leave"
808
+ | "outbound"
809
+ | "vital";
604
810
  visitorId: string;
605
811
  }>,
606
812
  Name
607
813
  >;
814
+ vitalsReport: FunctionReference<
815
+ "query",
816
+ "internal",
817
+ { from: number; siteId: string; to: number },
818
+ {
819
+ metadata: {
820
+ basis: "dailyRollup" | "boundedLegacy";
821
+ coverage: {
822
+ availableFrom: number | null;
823
+ availableThrough: number | null;
824
+ generation: number | null;
825
+ retainedBefore: number | null;
826
+ state: "complete" | "partial" | "retained" | "unavailable";
827
+ };
828
+ range: {
829
+ boundary: "inclusive";
830
+ from: number;
831
+ timezone: string;
832
+ to: number;
833
+ };
834
+ };
835
+ metrics: Array<{
836
+ average: number;
837
+ count: number;
838
+ devices: Array<{
839
+ average: number;
840
+ count: number;
841
+ device: string;
842
+ good: number;
843
+ needsImprovement: number;
844
+ p75: number;
845
+ poor: number;
846
+ }>;
847
+ good: number;
848
+ metric: "LCP" | "CLS" | "INP" | "FCP" | "TTFB";
849
+ needsImprovement: number;
850
+ p75: number;
851
+ pages: Array<{
852
+ average: number;
853
+ count: number;
854
+ good: number;
855
+ needsImprovement: number;
856
+ p75: number;
857
+ page: string;
858
+ poor: number;
859
+ }>;
860
+ poor: number;
861
+ timeSeries: Array<{
862
+ count: number;
863
+ p75: number;
864
+ timestamp: number;
865
+ }>;
866
+ }>;
867
+ truncated: boolean;
868
+ },
869
+ Name
870
+ >;
608
871
  };
609
872
  retention: {
610
873
  cleanup: FunctionReference<
@@ -623,13 +886,111 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
623
886
  | "funnelProgress"
624
887
  | "funnelSteps"
625
888
  | "affiliateStats"
889
+ | "goalRollups"
890
+ | "funnelRollups"
891
+ | "affiliateRollups"
892
+ | "vitalRollups"
893
+ | "siteMapRollups"
626
894
  | "attributions"
627
895
  | "liveSessions"
628
896
  | "ingestWindows"
629
897
  | "siteIngestWindows";
630
898
  siteId: string;
631
899
  },
632
- { deleted: number; hasMore: boolean },
900
+ { before: number; deleted: number; hasMore: boolean },
901
+ Name
902
+ >;
903
+ disablePolicy: FunctionReference<
904
+ "mutation",
905
+ "internal",
906
+ { siteId: string },
907
+ null,
908
+ Name
909
+ >;
910
+ runPolicy: FunctionReference<
911
+ "mutation",
912
+ "internal",
913
+ {
914
+ batchSize?: number;
915
+ kind:
916
+ | "events"
917
+ | "sessions"
918
+ | "conversions"
919
+ | "goalCompletions"
920
+ | "aggregateHour"
921
+ | "aggregateDay"
922
+ | "funnelProgress"
923
+ | "funnelSteps"
924
+ | "affiliateStats"
925
+ | "goalRollups"
926
+ | "funnelRollups"
927
+ | "affiliateRollups"
928
+ | "vitalRollups"
929
+ | "siteMapRollups"
930
+ | "attributions"
931
+ | "liveSessions"
932
+ | "ingestWindows"
933
+ | "siteIngestWindows";
934
+ siteId: string;
935
+ },
936
+ { before: number; deleted: number; hasMore: boolean },
937
+ Name
938
+ >;
939
+ setPolicy: FunctionReference<
940
+ "mutation",
941
+ "internal",
942
+ {
943
+ aggregatesDays: number;
944
+ conversionsDays: number;
945
+ eventsDays: number;
946
+ reportsDays: number;
947
+ sessionsDays: number;
948
+ siteId: string;
949
+ },
950
+ null,
951
+ Name
952
+ >;
953
+ status: FunctionReference<
954
+ "query",
955
+ "internal",
956
+ { siteId: string },
957
+ {
958
+ backlog: number;
959
+ jobs: Array<{
960
+ before: number;
961
+ deleted: number;
962
+ kind:
963
+ | "events"
964
+ | "sessions"
965
+ | "conversions"
966
+ | "goalCompletions"
967
+ | "aggregateHour"
968
+ | "aggregateDay"
969
+ | "funnelProgress"
970
+ | "funnelSteps"
971
+ | "affiliateStats"
972
+ | "goalRollups"
973
+ | "funnelRollups"
974
+ | "affiliateRollups"
975
+ | "vitalRollups"
976
+ | "siteMapRollups"
977
+ | "attributions"
978
+ | "liveSessions"
979
+ | "ingestWindows"
980
+ | "siteIngestWindows";
981
+ scheduledAt: number | null;
982
+ status: "pending" | "running" | "complete";
983
+ updatedAt: number;
984
+ }>;
985
+ policy: {
986
+ aggregatesDays: number;
987
+ conversionsDays: number;
988
+ eventsDays: number;
989
+ reportsDays: number;
990
+ sessionsDays: number;
991
+ updatedAt: number;
992
+ } | null;
993
+ },
633
994
  Name
634
995
  >;
635
996
  };
@@ -687,6 +1048,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
687
1048
  }>,
688
1049
  Name
689
1050
  >;
1051
+ localDays: FunctionReference<
1052
+ "query",
1053
+ "internal",
1054
+ { siteId: string },
1055
+ { since: number; timezone: string } | null,
1056
+ Name
1057
+ >;
690
1058
  update: FunctionReference<
691
1059
  "mutation",
692
1060
  "internal",
@@ -703,4 +1071,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
703
1071
  Name
704
1072
  >;
705
1073
  };
1074
+ visitors: {
1075
+ link: FunctionReference<
1076
+ "mutation",
1077
+ "internal",
1078
+ { previousVisitorId: string; siteId: string; visitorId: string },
1079
+ { aliasCount: number; linked: boolean },
1080
+ Name
1081
+ >;
1082
+ };
706
1083
  };
@@ -39,6 +39,10 @@ import type { DataModel } from "./dataModel.js";
39
39
  type Env = {
40
40
  readonly CONVEX_CLOUD_URL: string;
41
41
  readonly CONVEX_SITE_URL: string;
42
+ readonly RASTRO_GEOIP_DAILY_LIMIT: string | undefined;
43
+ readonly RASTRO_GEOIP_PROVIDER: "ipinfo" | "ipwhois" | undefined;
44
+ readonly RASTRO_GEOIP_TOKEN: string | undefined;
45
+ readonly RASTRO_TRUST_PROXY: "true" | undefined;
42
46
  };
43
47
 
44
48
  /**
@@ -22,16 +22,27 @@ export const upsert = mutation({
22
22
  },
23
23
  returns: v.id("affiliates"),
24
24
  handler: async (ctx, args) => {
25
- if (!(await ctx.db.get("sites", args.siteId))) fail("NOT_FOUND", "site not found");
26
- if (!Number.isSafeInteger(args.commissionBps) || args.commissionBps < 0 || args.commissionBps > 10_000) {
27
- fail("INVALID_ARGUMENT", "commissionBps must be an integer between 0 and 10000");
25
+ if (!(await ctx.db.get("sites", args.siteId)))
26
+ fail("NOT_FOUND", "site not found");
27
+ if (
28
+ !Number.isSafeInteger(args.commissionBps) ||
29
+ args.commissionBps < 0 ||
30
+ args.commissionBps > 10_000
31
+ ) {
32
+ fail(
33
+ "INVALID_ARGUMENT",
34
+ "commissionBps must be an integer between 0 and 10000",
35
+ );
28
36
  }
29
37
  if (
30
38
  !Number.isSafeInteger(args.attributionWindowDays) ||
31
39
  args.attributionWindowDays < 1 ||
32
40
  args.attributionWindowDays > 365
33
41
  ) {
34
- fail("INVALID_ARGUMENT", "attributionWindowDays must be an integer between 1 and 365");
42
+ fail(
43
+ "INVALID_ARGUMENT",
44
+ "attributionWindowDays must be an integer between 1 and 365",
45
+ );
35
46
  }
36
47
  let slug: string;
37
48
  try {
@@ -52,7 +63,11 @@ export const upsert = mutation({
52
63
  )
53
64
  .unique();
54
65
  const affiliateId = args.affiliateId ?? existingBySlug?._id;
55
- if (args.affiliateId && existingBySlug && existingBySlug._id !== args.affiliateId) {
66
+ if (
67
+ args.affiliateId &&
68
+ existingBySlug &&
69
+ existingBySlug._id !== args.affiliateId
70
+ ) {
56
71
  fail("CONFLICT", "another affiliate already uses this slug");
57
72
  }
58
73
  const now = Date.now();
@@ -0,0 +1,117 @@
1
+ const SKETCH_VERSION = 1 as const;
2
+ const DEFAULT_PRECISION = 6;
3
+
4
+ export type VisitorSketch = {
5
+ version: typeof SKETCH_VERSION;
6
+ precision: number;
7
+ registers: ArrayBuffer;
8
+ };
9
+
10
+ export function createVisitorSketch(
11
+ precision = DEFAULT_PRECISION,
12
+ ): VisitorSketch {
13
+ if (!Number.isSafeInteger(precision) || precision < 4 || precision > 10) {
14
+ throw new Error("visitor sketch precision must be between 4 and 10");
15
+ }
16
+ return {
17
+ version: SKETCH_VERSION,
18
+ precision,
19
+ registers: new Uint8Array(1 << precision).buffer,
20
+ };
21
+ }
22
+
23
+ export function addVisitor(
24
+ sketch: VisitorSketch,
25
+ visitorId: string,
26
+ ): VisitorSketch {
27
+ const registers = checkedRegisters(sketch).slice();
28
+ const hash = fnv1a64(visitorId);
29
+ const remainingBits = 64 - sketch.precision;
30
+ const index = Number(hash >> BigInt(remainingBits));
31
+ const remaining = hash & ((1n << BigInt(remainingBits)) - 1n);
32
+ const rank = leadingZeroRank(remaining, remainingBits);
33
+ registers[index] = Math.max(registers[index] ?? 0, rank);
34
+ return { ...sketch, registers: registers.buffer };
35
+ }
36
+
37
+ export function unionVisitorSketches(
38
+ left: VisitorSketch,
39
+ right: VisitorSketch,
40
+ ): VisitorSketch {
41
+ const leftRegisters = checkedRegisters(left);
42
+ const rightRegisters = checkedRegisters(right);
43
+ if (
44
+ left.version !== right.version ||
45
+ left.precision !== right.precision ||
46
+ leftRegisters.length !== rightRegisters.length
47
+ ) {
48
+ throw new Error("visitor sketches are incompatible");
49
+ }
50
+ const registers = leftRegisters.slice();
51
+ for (let index = 0; index < registers.length; index += 1) {
52
+ registers[index] = Math.max(
53
+ registers[index] ?? 0,
54
+ rightRegisters[index] ?? 0,
55
+ );
56
+ }
57
+ return { ...left, registers: registers.buffer };
58
+ }
59
+
60
+ export function estimateVisitors(sketch: VisitorSketch): number {
61
+ const registers = checkedRegisters(sketch);
62
+ const count = registers.length;
63
+ const alpha =
64
+ count === 16
65
+ ? 0.673
66
+ : count === 32
67
+ ? 0.697
68
+ : count === 64
69
+ ? 0.709
70
+ : 0.7213 / (1 + 1.079 / count);
71
+ let denominator = 0;
72
+ let empty = 0;
73
+ for (const register of registers) {
74
+ denominator += 2 ** -register;
75
+ if (register === 0) empty += 1;
76
+ }
77
+ let estimate = (alpha * count * count) / denominator;
78
+ if (estimate <= 2.5 * count && empty > 0) {
79
+ estimate = count * Math.log(count / empty);
80
+ }
81
+ return Math.max(0, Math.round(estimate));
82
+ }
83
+
84
+ function checkedRegisters(sketch: VisitorSketch): Uint8Array<ArrayBuffer> {
85
+ if (sketch.version !== SKETCH_VERSION) {
86
+ throw new Error("unsupported visitor sketch version");
87
+ }
88
+ const registers = new Uint8Array(sketch.registers);
89
+ if (registers.length !== 1 << sketch.precision) {
90
+ throw new Error("visitor sketch has invalid register storage");
91
+ }
92
+ return registers;
93
+ }
94
+
95
+ function leadingZeroRank(value: bigint, bits: number): number {
96
+ if (value === 0n) return bits + 1;
97
+ let rank = 1;
98
+ for (let bit = bits - 1; bit >= 0; bit -= 1) {
99
+ if ((value & (1n << BigInt(bit))) !== 0n) break;
100
+ rank += 1;
101
+ }
102
+ return rank;
103
+ }
104
+
105
+ function fnv1a64(value: string): bigint {
106
+ let hash = 0xcbf29ce484222325n;
107
+ for (const byte of new TextEncoder().encode(value)) {
108
+ hash ^= BigInt(byte);
109
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
110
+ }
111
+ hash ^= hash >> 33n;
112
+ hash = BigInt.asUintN(64, hash * 0xff51afd7ed558ccdn);
113
+ hash ^= hash >> 33n;
114
+ hash = BigInt.asUintN(64, hash * 0xc4ceb9fe1a85ec53n);
115
+ hash ^= hash >> 33n;
116
+ return hash;
117
+ }