@iann29/rastro 0.1.0-alpha.2 → 0.1.0-alpha.3

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 (70) hide show
  1. package/README.md +79 -30
  2. package/dist/component/_generated/api.d.ts +5 -5
  3. package/dist/component/_generated/api.d.ts.map +1 -1
  4. package/dist/component/_generated/api.js.map +1 -1
  5. package/dist/component/_generated/component.d.ts +10 -0
  6. package/dist/component/_generated/component.d.ts.map +1 -1
  7. package/dist/component/_generated/server.d.ts +4 -0
  8. package/dist/component/_generated/server.d.ts.map +1 -1
  9. package/dist/component/_generated/server.js.map +1 -1
  10. package/dist/component/convex.config.d.ts +7 -2
  11. package/dist/component/convex.config.d.ts.map +1 -1
  12. package/dist/component/convex.config.js +9 -4
  13. package/dist/component/convex.config.js.map +1 -1
  14. package/dist/component/diagnostics.d.ts +9 -0
  15. package/dist/component/diagnostics.d.ts.map +1 -0
  16. package/dist/component/diagnostics.js +47 -0
  17. package/dist/component/diagnostics.js.map +1 -0
  18. package/dist/component/eventStore.d.ts +11 -9
  19. package/dist/component/eventStore.d.ts.map +1 -1
  20. package/dist/component/eventStore.js +55 -227
  21. package/dist/component/eventStore.js.map +1 -1
  22. package/dist/component/geo.d.ts +71 -0
  23. package/dist/component/geo.d.ts.map +1 -0
  24. package/dist/component/geo.js +610 -0
  25. package/dist/component/geo.js.map +1 -0
  26. package/dist/component/http.d.ts.map +1 -1
  27. package/dist/component/http.js +124 -11
  28. package/dist/component/http.js.map +1 -1
  29. package/dist/component/ingest.d.ts.map +1 -1
  30. package/dist/component/ingest.js +34 -14
  31. package/dist/component/ingest.js.map +1 -1
  32. package/dist/component/reports.d.ts +8 -8
  33. package/dist/component/reports.d.ts.map +1 -1
  34. package/dist/component/reports.js +2 -1
  35. package/dist/component/reports.js.map +1 -1
  36. package/dist/component/schema.d.ts +15 -52
  37. package/dist/component/schema.js +14 -16
  38. package/dist/component/schema.js.map +1 -1
  39. package/dist/component/validators.d.ts +3 -50
  40. package/dist/component/validators.d.ts.map +1 -1
  41. package/dist/component/validators.js +4 -12
  42. package/dist/component/validators.js.map +1 -1
  43. package/dist/tracker/generated.d.ts +4 -4
  44. package/dist/tracker/generated.d.ts.map +1 -1
  45. package/dist/tracker/generated.js +4 -4
  46. package/dist/tracker/generated.js.map +1 -1
  47. package/dist/tracker/tracker.js +7 -6
  48. package/dist/tracker/tracker.js.map +1 -1
  49. package/dist/tracker.min.js +1 -1
  50. package/docs/benchmarks/2026-08-20-realistic.md +1 -1
  51. package/package.json +2 -6
  52. package/scripts/benchmark-ingest.mjs +81 -32
  53. package/src/component/_generated/api.ts +5 -5
  54. package/src/component/_generated/component.ts +13 -0
  55. package/src/component/_generated/server.ts +4 -0
  56. package/src/component/convex.config.ts +11 -5
  57. package/src/component/diagnostics.ts +64 -0
  58. package/src/component/eventStore.ts +78 -280
  59. package/src/component/geo.ts +779 -0
  60. package/src/component/http.ts +184 -17
  61. package/src/component/ingest.ts +49 -15
  62. package/src/component/reports.ts +4 -1
  63. package/src/component/schema.ts +15 -16
  64. package/src/component/validators.ts +4 -13
  65. package/src/test.ts +0 -2
  66. package/dist/component/migrations.d.ts +0 -17
  67. package/dist/component/migrations.d.ts.map +0 -1
  68. package/dist/component/migrations.js +0 -44
  69. package/dist/component/migrations.js.map +0 -1
  70. package/src/component/migrations.ts +0 -52
@@ -1,5 +1,5 @@
1
- export declare const TRACKER_SOURCE = "\"use strict\";(()=>{const t=document.currentScript,e=t?.dataset.site,o=t?.dataset.endpoint||t?.src.replace(/tracker\\.js.*$/,\"events\");if(!e||!o)return;const s=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search).get(\"ref\")?.slice(0,64);let r,a;try{r=sessionStorage._r||s(),sessionStorage._r=r,a=n||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=s(),a=n}const i=t.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=t=>{const s=l.splice(0,50);if(!s.length)return;const n=()=>new Blob([JSON.stringify({siteId:e,events:s})]);let r=n();for(;r.size>64e3&&s.length>1;)l.unshift(s.pop()),r=n();t&&navigator.sendBeacon(o,r)||fetch(o,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),l.length&&p(t)},g=(t,e,o)=>{const s=Date.now();l.push({eventId:`${r}.${s.toString(36)}.${d.toString(36)}`,sessionId:r,visitorId:i,type:t,name:e,path:location.pathname.slice(0,256),referrer:h,timestamp:s,sequence:d++,affiliateSlug:a,...o}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const t=location.pathname;t!==u&&(u=t,g(\"pageview\"))};window.rastro=(t,e,o)=>{if(\"context\"===t)return{sessionId:r,visitorId:i};\"event\"===t&&g(\"custom\",`${e||\"event\"}`.slice(0,80),o&&JSON.stringify(o).length>2e4?void 0:{properties:o})},document.addEventListener(\"click\",t=>{const e=t.target?.closest(\"a,button,[data-rastro-event]\");if(!e)return;const o=e.dataset.rastroEvent,s=e.href,n=s?new URL(s):void 0,r=\"http:\"===n?.protocol||\"https:\"===n?.protocol;g(o?\"custom\":r&&n.origin!==location.origin?\"outbound\":\"click\",o,{target:(e.dataset.rastroLabel||e.tagName).slice(0,80),href:r?n.pathname.slice(0,256):void 0})},1);const m=t=>{const e=history[t];history[t]=function(t,o,s){e.call(history,t,o,s),v()}};m(\"pushState\"),m(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>p(1)),setInterval(()=>{g(\"heartbeat\"),p()},2e4),v()})();";
2
- export declare const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VV227cNhD9FZswBLKZMGvHMQoJ9AJB+uDCTYJu8uQaNU3NSqwlUiVH67or/Xuhy65vzdvqDLWcOZcRayMeRArWEMs4F+p8a7yLdEAq96at0ZE0bQjoaGWCbQhQ0VLmmnREktESgn+KoMsbbx11HS1lDEYGbCptkL+joM0dhj/kX1H+dPQOGG7QUWQis2t+iF136EVAaoPLphaiGvox4aEhL4N2ua+/f7/4tJRcdN1vmsoZ5OINY+CUw/uD779frlAHU37VQdeRV95ost7JOKJCFkicBVwzsZSxsgb5As5ORVYhHQTQGYWHbVARY7TercgHXaD8M3Rd5AJewSqAVq7rXhY06CThr1ClRW80mXK4govh3X6a1SraU7ix0ZIPXRfGrgzkajFTUqmraygfpQm4xhAwLOfZ+auKkD7Ywrp0421+sIBG0V7jqCoZm5mED4tJiCgrdAWVz7VwoxbDLR8rf8uvfl19+SwH37jCrh/4djDCRZ4iTKKmsRfXM6nKcZGtfeBZkNH+i+dnp/g+SXb3nB9nopKti6VdE4+y8Q0XAqbXKEmc3thCkw8yoss/ojbecQ9BdN0ayZTcw7ZGKn2esq9fVt8Y3Pr8IQ1wh9joym4wPe6FHGmfDN4LqObLk6ThJHooFCdA8OIJN580oXT+nouskk0bS74dh7vI05ujbejl0TZK8quRBP7+TAxI/hy52VnmIk8DzMJe5KkFemgwJXC6xhSh0VSme68OT0Nhb9CTD2cCdoqmJZCtMZKumzRCxL9bdAbT/M0b0Ou1rawmXFVtkWqQUvpegKlQh2+2Rt8SNwKMiki75waOTxYL0Y9qtbPeG/VsF7xqLaNDpdok4a0iKDhrdIEbi/dMiD67ty739zLoSME/YdauOTPeEf5DTClFs8W2P+Coz6YVMZ5NkoIz00byNYOboy123Vztb/Y8/bwQ4JPkhTm92FntBE+XUw7SbRN8g4EsxtT3ood9cnSe/zL88aWNhA4DZ6ay5o7BY3BQkSQdCqSlNJWPGIkzDbctkXdwNQT57TT927HH692Sex4qr3Af+un4eDFEhbIMuAan4j7ZUewiHBQriZp04MUtZRM8eeOrrhvh+BLPCu6XO+rSkCRuXgmH6lHXCVky39Ktb13O0t3UHrbTqCl/2e2lvsWq61CSLj7rGsUzIYYJ0rD8gZ3nYQbmj8XMR62eUlzaSD48XNF19vhTrVtnho45gYcotiiNrio+n4AJhQ0XfZ/VnA25XZEmZALqYfGPH6Md8lrqxjdxLMLmf8u6wNLmyGCIR8OPxfBRoAtHGDa6mvZLwVmJOtAtamICGi56OMHTqSvBRfYfmqHy1nUHAAA=";
3
- export declare const TRACKER_RAW_BYTES = 1909;
4
- export declare const TRACKER_GZIP_BYTES = 1022;
1
+ export declare const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",o=new URLSearchParams(location.search).get(\"ref\")?.slice(0,64);let r,a;try{r=sessionStorage._r||n(),sessionStorage._r=r,a=o||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=n(),a=o}const i=e.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=e=>{const n=l.splice(0,50);if(!n.length)return;const o=()=>new Blob([JSON.stringify({siteId:t,events:n})]);let r=o();for(;r.size>64e3&&n.length>1;)l.unshift(n.pop()),r=o();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),l.length&&p(e)},g=(e,t,s)=>{const n=Date.now();l.push({eventId:`${r}.${n.toString(36)}.${d}`,sessionId:r,visitorId:i,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:n,sequence:d++,affiliateSlug:a,...s}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const e=location.pathname;e!==u&&(u=e,g(\"pageview\"))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:r,visitorId:i};\"event\"===e&&g(\"custom\",`${t||\"event\"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener(\"click\",e=>{const t=e.target?.closest(\"a,button,[data-rastro-event]\");if(!t)return;const s=t.dataset.rastroEvent,n=t.href,o=n&&new URL(n),r=/^https?:$/.test(o?.protocol);g(s?\"custom\":r&&o.origin!==location.origin?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.tagName).slice(0,80),href:r?o.pathname.slice(0,256):void 0})},1);const m=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};m(\"pushState\"),m(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>p(1)),setInterval(()=>{g(\"heartbeat\"),p()},2e4),v()})();";
2
+ export declare const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VUbW/bNhD+K4kQCCRyZZ00DQYJjIGi+5Aha4u5/RRkC0OdJaISyZEnZ56l/z7oxU7SdN+s52Td3fNyjHF5tdPORjpCWTjdNmhJ6DYEtLTSwXgCkrgUhSIVkUQ0hBCfI2gL74ylrsOliEGnqcXHo29/3LAEN2gpJoADzkUVcJ2bNTumrjuOPCC1weZTdyuHUXTYenIiKFu45tu3649LwXjX/a6omkHGT5MEnJx7rFAFXX1RQTWR1U4rMs6KOKJclEgsCbhO+FLE2mhkC7i84HmNdBRA5RS2uyAjxmicXZELqkTxV+g6yzi8gmUAJV3X/VhQoNKUvUKl4r1WpKtdkMP3lHT9tKuReGBvY6IhF7oujFNpKORipqSWt3dQPakScI0hYFju+X1V4cIFUxqbbZwpjhbgJR7ktbIW0c8kvF/wUQgrarQlVS+1cKMWQ5cPtXtgt7+tPn8SkYKxpVlv2W7wwHWREUz6ZrbndzOp0jGer11geRDR/ItXlxf4Lk33fa7Ocl6L1sbKrIlZ4Z1nnMP0N0xTqzamVOSCiGiLD6i0syxC4F23RtIVi7BrkCpXZMmXz6uvCTy4YpsF+I7oVW02mJ31XIy0s9HbPYd6bp6mniHvoZQMgSDyZ9x8VITCukfG81r4NlZsNy53XWT3J7vQi5OdFeRWIwns3SUfkKK/37vkusgCzFpeF5kB2nrMEKxqMCPwiqrsYM/haSgcPHn+/pLDXsSsAjINRlKNzyxE/LtFqzErTk9BrdemNopwVbdlpkAIEXsOukYVvpoGXUtMc9AyIu2fPZydLxa8HwVqZ4k38kXyX42W47GUbZqyViKULPGqxI3Bx4TzPn80tnCPIqhIwT0j06xZop0l/IcSKSXOrtr9D0d9Ph2I8d00LVmi20iuSeD+ZEddN1f7+wNPvyw4xDT9wY+R7911jhfLyfrZzgfnMZDBmMWe93AIiyqKX4cP35hIaDGwRNdGf0/gKSskUZAKJdJS6NpFjMQSBQ8tkbNwO2T3zbT9m3HGu2SKE73MUZR0yPn0+tgYrKTxFg5n7Ola2iEHb/+siHxcZidvBQ1t3VL44MhpV/O8ZHG5JykLaermvB/LJwUnZJm4lh5ca4sk2+8XYTctlbEf57pRD1h3HQlS5SfVIH9B+TBrFpbu58adj83A8RmfN2/kczIrE8mF7S3e5U8/JRNC4GAbEsr7esvmGiCHDeN9nzcsGaK4IkWYcGiGW+5rpXGPvJbSOx/HImx+WlYlVqbABAb7e3bGhztP15YwbFQ9nYySJRWqQA+oKOHgGe/hHC+mqTjj+X/ssrhNNgcAAA==";
3
+ export declare const TRACKER_RAW_BYTES = 1846;
4
+ export declare const TRACKER_GZIP_BYTES = 1000;
5
5
  //# sourceMappingURL=generated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,u6DAAu6D,CAAC;AACn8D,eAAO,MAAM,mBAAmB,y1CAAy1C,CAAC;AAC13C,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,kBAAkB,OAAO,CAAC"}
1
+ {"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,i2DAAi2D,CAAC;AAC73D,eAAO,MAAM,mBAAmB,6zCAA6zC,CAAC;AAC91C,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,kBAAkB,OAAO,CAAC"}
@@ -1,6 +1,6 @@
1
1
  // Generated by scripts/build-tracker.mjs. Do not edit.
2
- export const TRACKER_SOURCE = "\"use strict\";(()=>{const t=document.currentScript,e=t?.dataset.site,o=t?.dataset.endpoint||t?.src.replace(/tracker\\.js.*$/,\"events\");if(!e||!o)return;const s=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search).get(\"ref\")?.slice(0,64);let r,a;try{r=sessionStorage._r||s(),sessionStorage._r=r,a=n||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=s(),a=n}const i=t.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=t=>{const s=l.splice(0,50);if(!s.length)return;const n=()=>new Blob([JSON.stringify({siteId:e,events:s})]);let r=n();for(;r.size>64e3&&s.length>1;)l.unshift(s.pop()),r=n();t&&navigator.sendBeacon(o,r)||fetch(o,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),l.length&&p(t)},g=(t,e,o)=>{const s=Date.now();l.push({eventId:`${r}.${s.toString(36)}.${d.toString(36)}`,sessionId:r,visitorId:i,type:t,name:e,path:location.pathname.slice(0,256),referrer:h,timestamp:s,sequence:d++,affiliateSlug:a,...o}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const t=location.pathname;t!==u&&(u=t,g(\"pageview\"))};window.rastro=(t,e,o)=>{if(\"context\"===t)return{sessionId:r,visitorId:i};\"event\"===t&&g(\"custom\",`${e||\"event\"}`.slice(0,80),o&&JSON.stringify(o).length>2e4?void 0:{properties:o})},document.addEventListener(\"click\",t=>{const e=t.target?.closest(\"a,button,[data-rastro-event]\");if(!e)return;const o=e.dataset.rastroEvent,s=e.href,n=s?new URL(s):void 0,r=\"http:\"===n?.protocol||\"https:\"===n?.protocol;g(o?\"custom\":r&&n.origin!==location.origin?\"outbound\":\"click\",o,{target:(e.dataset.rastroLabel||e.tagName).slice(0,80),href:r?n.pathname.slice(0,256):void 0})},1);const m=t=>{const e=history[t];history[t]=function(t,o,s){e.call(history,t,o,s),v()}};m(\"pushState\"),m(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>p(1)),setInterval(()=>{g(\"heartbeat\"),p()},2e4),v()})();";
3
- export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VV227cNhD9FZswBLKZMGvHMQoJ9AJB+uDCTYJu8uQaNU3NSqwlUiVH67or/Xuhy65vzdvqDLWcOZcRayMeRArWEMs4F+p8a7yLdEAq96at0ZE0bQjoaGWCbQhQ0VLmmnREktESgn+KoMsbbx11HS1lDEYGbCptkL+joM0dhj/kX1H+dPQOGG7QUWQis2t+iF136EVAaoPLphaiGvox4aEhL4N2ua+/f7/4tJRcdN1vmsoZ5OINY+CUw/uD779frlAHU37VQdeRV95ost7JOKJCFkicBVwzsZSxsgb5As5ORVYhHQTQGYWHbVARY7TercgHXaD8M3Rd5AJewSqAVq7rXhY06CThr1ClRW80mXK4govh3X6a1SraU7ix0ZIPXRfGrgzkajFTUqmraygfpQm4xhAwLOfZ+auKkD7Ywrp0421+sIBG0V7jqCoZm5mED4tJiCgrdAWVz7VwoxbDLR8rf8uvfl19+SwH37jCrh/4djDCRZ4iTKKmsRfXM6nKcZGtfeBZkNH+i+dnp/g+SXb3nB9nopKti6VdE4+y8Q0XAqbXKEmc3thCkw8yoss/ojbecQ9BdN0ayZTcw7ZGKn2esq9fVt8Y3Pr8IQ1wh9joym4wPe6FHGmfDN4LqObLk6ThJHooFCdA8OIJN580oXT+nouskk0bS74dh7vI05ujbejl0TZK8quRBP7+TAxI/hy52VnmIk8DzMJe5KkFemgwJXC6xhSh0VSme68OT0Nhb9CTD2cCdoqmJZCtMZKumzRCxL9bdAbT/M0b0Ou1rawmXFVtkWqQUvpegKlQh2+2Rt8SNwKMiki75waOTxYL0Y9qtbPeG/VsF7xqLaNDpdok4a0iKDhrdIEbi/dMiD67ty739zLoSME/YdauOTPeEf5DTClFs8W2P+Coz6YVMZ5NkoIz00byNYOboy123Vztb/Y8/bwQ4JPkhTm92FntBE+XUw7SbRN8g4EsxtT3ood9cnSe/zL88aWNhA4DZ6ay5o7BY3BQkSQdCqSlNJWPGIkzDbctkXdwNQT57TT927HH692Sex4qr3Af+un4eDFEhbIMuAan4j7ZUewiHBQriZp04MUtZRM8eeOrrhvh+BLPCu6XO+rSkCRuXgmH6lHXCVky39Ktb13O0t3UHrbTqCl/2e2lvsWq61CSLj7rGsUzIYYJ0rD8gZ3nYQbmj8XMR62eUlzaSD48XNF19vhTrVtnho45gYcotiiNrio+n4AJhQ0XfZ/VnA25XZEmZALqYfGPH6Md8lrqxjdxLMLmf8u6wNLmyGCIR8OPxfBRoAtHGDa6mvZLwVmJOtAtamICGi56OMHTqSvBRfYfmqHy1nUHAAA=";
4
- export const TRACKER_RAW_BYTES = 1909;
5
- export const TRACKER_GZIP_BYTES = 1022;
2
+ export const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",o=new URLSearchParams(location.search).get(\"ref\")?.slice(0,64);let r,a;try{r=sessionStorage._r||n(),sessionStorage._r=r,a=o||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=n(),a=o}const i=e.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=e=>{const n=l.splice(0,50);if(!n.length)return;const o=()=>new Blob([JSON.stringify({siteId:t,events:n})]);let r=o();for(;r.size>64e3&&n.length>1;)l.unshift(n.pop()),r=o();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),l.length&&p(e)},g=(e,t,s)=>{const n=Date.now();l.push({eventId:`${r}.${n.toString(36)}.${d}`,sessionId:r,visitorId:i,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:n,sequence:d++,affiliateSlug:a,...s}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const e=location.pathname;e!==u&&(u=e,g(\"pageview\"))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:r,visitorId:i};\"event\"===e&&g(\"custom\",`${t||\"event\"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener(\"click\",e=>{const t=e.target?.closest(\"a,button,[data-rastro-event]\");if(!t)return;const s=t.dataset.rastroEvent,n=t.href,o=n&&new URL(n),r=/^https?:$/.test(o?.protocol);g(s?\"custom\":r&&o.origin!==location.origin?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.tagName).slice(0,80),href:r?o.pathname.slice(0,256):void 0})},1);const m=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};m(\"pushState\"),m(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>p(1)),setInterval(()=>{g(\"heartbeat\"),p()},2e4),v()})();";
3
+ export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VUbW/bNhD+K4kQCCRyZZ00DQYJjIGi+5Aha4u5/RRkC0OdJaISyZEnZ56l/z7oxU7SdN+s52Td3fNyjHF5tdPORjpCWTjdNmhJ6DYEtLTSwXgCkrgUhSIVkUQ0hBCfI2gL74ylrsOliEGnqcXHo29/3LAEN2gpJoADzkUVcJ2bNTumrjuOPCC1weZTdyuHUXTYenIiKFu45tu3649LwXjX/a6omkHGT5MEnJx7rFAFXX1RQTWR1U4rMs6KOKJclEgsCbhO+FLE2mhkC7i84HmNdBRA5RS2uyAjxmicXZELqkTxV+g6yzi8gmUAJV3X/VhQoNKUvUKl4r1WpKtdkMP3lHT9tKuReGBvY6IhF7oujFNpKORipqSWt3dQPakScI0hYFju+X1V4cIFUxqbbZwpjhbgJR7ktbIW0c8kvF/wUQgrarQlVS+1cKMWQ5cPtXtgt7+tPn8SkYKxpVlv2W7wwHWREUz6ZrbndzOp0jGer11geRDR/ItXlxf4Lk33fa7Ocl6L1sbKrIlZ4Z1nnMP0N0xTqzamVOSCiGiLD6i0syxC4F23RtIVi7BrkCpXZMmXz6uvCTy4YpsF+I7oVW02mJ31XIy0s9HbPYd6bp6mniHvoZQMgSDyZ9x8VITCukfG81r4NlZsNy53XWT3J7vQi5OdFeRWIwns3SUfkKK/37vkusgCzFpeF5kB2nrMEKxqMCPwiqrsYM/haSgcPHn+/pLDXsSsAjINRlKNzyxE/LtFqzErTk9BrdemNopwVbdlpkAIEXsOukYVvpoGXUtMc9AyIu2fPZydLxa8HwVqZ4k38kXyX42W47GUbZqyViKULPGqxI3Bx4TzPn80tnCPIqhIwT0j06xZop0l/IcSKSXOrtr9D0d9Ph2I8d00LVmi20iuSeD+ZEddN1f7+wNPvyw4xDT9wY+R7911jhfLyfrZzgfnMZDBmMWe93AIiyqKX4cP35hIaDGwRNdGf0/gKSskUZAKJdJS6NpFjMQSBQ8tkbNwO2T3zbT9m3HGu2SKE73MUZR0yPn0+tgYrKTxFg5n7Ola2iEHb/+siHxcZidvBQ1t3VL44MhpV/O8ZHG5JykLaermvB/LJwUnZJm4lh5ca4sk2+8XYTctlbEf57pRD1h3HQlS5SfVIH9B+TBrFpbu58adj83A8RmfN2/kczIrE8mF7S3e5U8/JRNC4GAbEsr7esvmGiCHDeN9nzcsGaK4IkWYcGiGW+5rpXGPvJbSOx/HImx+WlYlVqbABAb7e3bGhztP15YwbFQ9nYySJRWqQA+oKOHgGe/hHC+mqTjj+X/ssrhNNgcAAA==";
4
+ export const TRACKER_RAW_BYTES = 1846;
5
+ export const TRACKER_GZIP_BYTES = 1000;
6
6
  //# sourceMappingURL=generated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generated.js","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,o6DAAo6D,CAAC;AACn8D,MAAM,CAAC,MAAM,mBAAmB,GAAG,s1CAAs1C,CAAC;AAC13C,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"generated.js","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,81DAA81D,CAAC;AAC73D,MAAM,CAAC,MAAM,mBAAmB,GAAG,0zCAA0zC,CAAC;AAC91C,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC"}
@@ -2,7 +2,8 @@
2
2
  (() => {
3
3
  const script = document.currentScript;
4
4
  const siteId = script?.dataset.site;
5
- const endpoint = script?.dataset.endpoint || script?.src.replace(/tracker\.js.*$/, "events");
5
+ const endpoint = script?.dataset.endpoint ||
6
+ (script?.src && new URL("events", script.src).href);
6
7
  if (!siteId || !endpoint)
7
8
  return;
8
9
  const createId = () => crypto.randomUUID?.() || Math.random() + "";
@@ -50,7 +51,7 @@
50
51
  const emit = (type, name, extra) => {
51
52
  const timestamp = Date.now();
52
53
  queue.push({
53
- eventId: `${sessionId}.${timestamp.toString(36)}.${sequence.toString(36)}`,
54
+ eventId: `${sessionId}.${timestamp.toString(36)}.${sequence}`,
54
55
  sessionId,
55
56
  visitorId,
56
57
  type,
@@ -88,8 +89,8 @@
88
89
  return;
89
90
  const name = element.dataset.rastroEvent;
90
91
  const href = element.href;
91
- const url = href ? new URL(href) : undefined;
92
- const web = url?.protocol === "http:" || url?.protocol === "https:";
92
+ const url = (href && new URL(href));
93
+ const web = /^https?:$/.test(url?.protocol);
93
94
  emit(name
94
95
  ? "custom"
95
96
  : web && url.origin !== location.origin
@@ -101,8 +102,8 @@
101
102
  }, true);
102
103
  const wrapHistory = (method) => {
103
104
  const native = history[method];
104
- history[method] = function (data, unused, url) {
105
- native.call(history, data, unused, url);
105
+ history[method] = function (...args) {
106
+ native.apply(history, args);
106
107
  pageview();
107
108
  };
108
109
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/tracker/tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/tracker/tracker.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- "use strict";(()=>{const t=document.currentScript,e=t?.dataset.site,o=t?.dataset.endpoint||t?.src.replace(/tracker\.js.*$/,"events");if(!e||!o)return;const s=()=>crypto.randomUUID?.()||Math.random()+"",n=new URLSearchParams(location.search).get("ref")?.slice(0,64);let r,a;try{r=sessionStorage._r||s(),sessionStorage._r=r,a=n||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=s(),a=n}const i=t.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=t=>{const s=l.splice(0,50);if(!s.length)return;const n=()=>new Blob([JSON.stringify({siteId:e,events:s})]);let r=n();for(;r.size>64e3&&s.length>1;)l.unshift(s.pop()),r=n();t&&navigator.sendBeacon(o,r)||fetch(o,{method:"POST",body:r,keepalive:1}).catch(()=>{}),l.length&&p(t)},g=(t,e,o)=>{const s=Date.now();l.push({eventId:`${r}.${s.toString(36)}.${d.toString(36)}`,sessionId:r,visitorId:i,type:t,name:e,path:location.pathname.slice(0,256),referrer:h,timestamp:s,sequence:d++,affiliateSlug:a,...o}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const t=location.pathname;t!==u&&(u=t,g("pageview"))};window.rastro=(t,e,o)=>{if("context"===t)return{sessionId:r,visitorId:i};"event"===t&&g("custom",`${e||"event"}`.slice(0,80),o&&JSON.stringify(o).length>2e4?void 0:{properties:o})},document.addEventListener("click",t=>{const e=t.target?.closest("a,button,[data-rastro-event]");if(!e)return;const o=e.dataset.rastroEvent,s=e.href,n=s?new URL(s):void 0,r="http:"===n?.protocol||"https:"===n?.protocol;g(o?"custom":r&&n.origin!==location.origin?"outbound":"click",o,{target:(e.dataset.rastroLabel||e.tagName).slice(0,80),href:r?n.pathname.slice(0,256):void 0})},1);const m=t=>{const e=history[t];history[t]=function(t,o,s){e.call(history,t,o,s),v()}};m("pushState"),m("replaceState"),addEventListener("popstate",v),addEventListener("pagehide",()=>p(1)),setInterval(()=>{g("heartbeat"),p()},2e4),v()})();
1
+ (()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL("events",e.src).href;if(!t||!s)return;const n=()=>crypto.randomUUID?.()||Math.random()+"",o=new URLSearchParams(location.search).get("ref")?.slice(0,64);let r,a;try{r=sessionStorage._r||n(),sessionStorage._r=r,a=o||sessionStorage._a,a&&(sessionStorage._a=a)}catch{r=n(),a=o}const i=e.dataset.visitor||r;let c,d=0;const l=[],h=document.referrer?new URL(document.referrer).origin:void 0,p=e=>{const n=l.splice(0,50);if(!n.length)return;const o=()=>new Blob([JSON.stringify({siteId:t,events:n})]);let r=o();for(;r.size>64e3&&n.length>1;)l.unshift(n.pop()),r=o();e&&navigator.sendBeacon(s,r)||fetch(s,{method:"POST",body:r,keepalive:1}).catch(()=>{}),l.length&&p(e)},g=(e,t,s)=>{const n=Date.now();l.push({eventId:`${r}.${n.toString(36)}.${d}`,sessionId:r,visitorId:i,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:n,sequence:d++,affiliateSlug:a,...s}),clearTimeout(c),c=setTimeout(p,1200)};let u;const v=()=>{const e=location.pathname;e!==u&&(u=e,g("pageview"))};window.rastro=(e,t,s)=>{if("context"===e)return{sessionId:r,visitorId:i};"event"===e&&g("custom",`${t||"event"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener("click",e=>{const t=e.target?.closest("a,button,[data-rastro-event]");if(!t)return;const s=t.dataset.rastroEvent,n=t.href,o=n&&new URL(n),r=/^https?:$/.test(o?.protocol);g(s?"custom":r&&o.origin!==location.origin?"outbound":"click",s,{target:(t.dataset.rastroLabel||t.tagName).slice(0,80),href:r?o.pathname.slice(0,256):void 0})},1);const m=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};m("pushState"),m("replaceState"),addEventListener("popstate",v),addEventListener("pagehide",()=>p(1)),setInterval(()=>{g("heartbeat"),p()},2e4),v()})();
@@ -150,7 +150,7 @@ A passing harness certification must include:
150
150
  - `certification.requested: true` and `certification.passed: true`;
151
151
  - zero request failures, duplicate events, rejected events, and failed events;
152
152
  - completed request accounting;
153
- - reliable and exact aggregate reconciliation;
153
+ - reliable and exact run-session reconciliation;
154
154
  - separate accepted and stored throughput/capacity fields.
155
155
 
156
156
  Release evidence should additionally record deployment health and successful
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "bugs": {
7
7
  "url": "https://github.com/amageweb/amage-rastro/issues"
8
8
  },
9
- "version": "0.1.0-alpha.2",
9
+ "version": "0.1.0-alpha.3",
10
10
  "license": "Apache-2.0",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -123,9 +123,5 @@
123
123
  "vitest": "4.1.10"
124
124
  },
125
125
  "types": "./dist/client/index.d.ts",
126
- "module": "./dist/client/index.js",
127
- "dependencies": {
128
- "@convex-dev/migrations": "^0.3.6",
129
- "convex-helpers": "^0.1.123"
130
- }
126
+ "module": "./dist/client/index.js"
131
127
  }
@@ -1,11 +1,14 @@
1
1
  import process from "node:process";
2
2
  import { Buffer } from "node:buffer";
3
+ import { randomUUID } from "node:crypto";
3
4
  import { performance } from "node:perf_hooks";
4
5
  import { clearInterval, setInterval } from "node:timers";
5
6
  import { ConvexHttpClient } from "convex/browser";
6
7
  import { makeFunctionReference } from "convex/server";
7
8
 
8
- const overviewQuery = makeFunctionReference("example:overview");
9
+ const reconcileSessionsQuery = makeFunctionReference(
10
+ "benchmark:reconcileSessionsPage",
11
+ );
9
12
 
10
13
  const PROFILE_DEFAULTS = {
11
14
  lean: { batchSize: 50, sessions: 0 },
@@ -200,6 +203,14 @@ function percentile(sorted, fraction) {
200
203
  return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1)];
201
204
  }
202
205
 
206
+ function hasValidSessionTotals(value) {
207
+ return value !== null &&
208
+ typeof value === "object" &&
209
+ [value.requestedSessions, value.foundSessions, value.storedEvents].every(
210
+ (total) => Number.isSafeInteger(total) && total >= 0,
211
+ );
212
+ }
213
+
203
214
  async function main() {
204
215
  const options = parseOptions(process.argv.slice(2));
205
216
  if (options.batchSize > 50) throw new Error("--batch-size cannot exceed 50");
@@ -212,7 +223,7 @@ async function main() {
212
223
  if (options.certify && Number.isFinite(options.maximumRequests)) {
213
224
  throw new Error("--certify cannot be combined with --requests");
214
225
  }
215
- const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
226
+ const runId = randomUUID();
216
227
  const sessions = Array.from({ length: options.sessionCount }, (_, index) => ({
217
228
  id: `bench-${runId}-${index}`,
218
229
  visitorId: `visitor-${runId}-${index}`,
@@ -288,20 +299,23 @@ async function main() {
288
299
  process.once("SIGINT", stopForInterrupt);
289
300
  process.once("SIGTERM", stopForTermination);
290
301
 
291
- const wallClockStartedAt = Date.now();
292
- const reconciliationFrom = Math.floor(wallClockStartedAt / 3_600_000) * 3_600_000;
293
- const reconciliationTo = Math.ceil(
294
- (wallClockStartedAt + options.durationSeconds * 1_000 + 60_000) / 3_600_000,
295
- ) * 3_600_000 - 1;
296
302
  const convex = options.convexUrl ? new ConvexHttpClient(options.convexUrl) : null;
297
- const aggregateBefore = convex
298
- ? await convex.query(overviewQuery, {
299
- siteIds: [options.siteId],
300
- from: reconciliationFrom,
301
- to: reconciliationTo,
302
- interval: "hour",
303
- })
304
- : null;
303
+ if (convex) {
304
+ const preflight = await convex.query(reconcileSessionsQuery, {
305
+ siteId: options.siteId,
306
+ runId,
307
+ start: 0,
308
+ count: 1,
309
+ });
310
+ if (
311
+ !hasValidSessionTotals(preflight) ||
312
+ preflight.requestedSessions !== 1 ||
313
+ preflight.foundSessions !== 0 ||
314
+ preflight.storedEvents !== 0
315
+ ) {
316
+ throw new Error("reconciliation preflight returned unexpected totals");
317
+ }
318
+ }
305
319
  const startedAt = performance.now();
306
320
  const deadline = startedAt + options.durationSeconds * 1_000;
307
321
  const recordError = (key) => errors.set(key, (errors.get(key) ?? 0) + 1);
@@ -392,31 +406,66 @@ async function main() {
392
406
 
393
407
  const elapsedMs = performance.now() - startedAt;
394
408
  terminationReason ??= performance.now() >= deadline ? "duration" : "completed";
395
- const aggregateAfter = convex
396
- ? await convex.query(overviewQuery, {
397
- siteIds: [options.siteId],
398
- from: reconciliationFrom,
399
- to: reconciliationTo,
400
- interval: "hour",
401
- })
402
- : null;
403
409
  process.removeListener("SIGINT", stopForInterrupt);
404
410
  process.removeListener("SIGTERM", stopForTermination);
405
411
  const durationCompleted = terminationReason === "duration";
406
412
  durations.sort((left, right) => left - right);
407
- const storedEvents = accepted - acceptedHeartbeats;
408
- const reconciliation = aggregateBefore && aggregateAfter
413
+ const expectedSessions = options.profile === "lean"
414
+ ? nextRequest
415
+ : Math.min(nextRequest, options.sessionCount);
416
+ let sessionTotals = null;
417
+ if (convex && expectedSessions > 0) {
418
+ try {
419
+ sessionTotals = {
420
+ requestedSessions: 0,
421
+ foundSessions: 0,
422
+ storedEvents: 0,
423
+ };
424
+ for (let start = 0; start < expectedSessions; start += 100) {
425
+ const page = await convex.query(reconcileSessionsQuery, {
426
+ siteId: options.siteId,
427
+ runId,
428
+ start,
429
+ count: Math.min(100, expectedSessions - start),
430
+ });
431
+ if (!hasValidSessionTotals(page)) {
432
+ throw new Error("reconciliation response has invalid totals");
433
+ }
434
+ sessionTotals.requestedSessions += page.requestedSessions;
435
+ sessionTotals.foundSessions += page.foundSessions;
436
+ sessionTotals.storedEvents += page.storedEvents;
437
+ }
438
+ } catch (error) {
439
+ reconciliationReliable = false;
440
+ sessionTotals = null;
441
+ recordError(
442
+ `reconciliation:${error instanceof Error ? error.message : "unknown"}`,
443
+ );
444
+ }
445
+ }
446
+ const expectedStoredEvents = reconciliationReliable
447
+ ? accepted - acceptedHeartbeats
448
+ : null;
449
+ const storedEvents = sessionTotals?.storedEvents ?? expectedStoredEvents;
450
+ const reconciliation = sessionTotals
409
451
  ? {
410
- scope: "site-wide-delta",
452
+ scope: "run-session-ids",
411
453
  reliable: reconciliationReliable && failures === 0 && rejected === 0,
412
- expectedStoredEvents: storedEvents,
413
- aggregateEventsDelta:
414
- aggregateAfter.totals.events - aggregateBefore.totals.events,
454
+ expectedSessions,
455
+ requestedSessions: sessionTotals.requestedSessions,
456
+ foundSessions: sessionTotals.foundSessions,
457
+ acceptedEvents: accepted,
458
+ acceptedHeartbeats: reconciliationReliable ? acceptedHeartbeats : null,
459
+ expectedStoredEvents,
460
+ storedEvents: sessionTotals.storedEvents,
415
461
  matches:
416
462
  reconciliationReliable &&
417
463
  failures === 0 &&
418
464
  rejected === 0 &&
419
- aggregateAfter.totals.events - aggregateBefore.totals.events === storedEvents,
465
+ expectedStoredEvents !== null &&
466
+ sessionTotals.requestedSessions === expectedSessions &&
467
+ sessionTotals.foundSessions === expectedSessions &&
468
+ sessionTotals.storedEvents === expectedStoredEvents,
420
469
  }
421
470
  : null;
422
471
  const cleanRun =
@@ -458,13 +507,13 @@ async function main() {
458
507
  errors: Object.fromEntries(errors),
459
508
  throughput: {
460
509
  eventsPerSecond: Math.round((accepted * 1_000) / elapsedMs),
461
- storedEventsPerSecond: reconciliationReliable
510
+ storedEventsPerSecond: reconciliationPassed
462
511
  ? Math.round((storedEvents * 1_000) / elapsedMs)
463
512
  : null,
464
513
  estimatedEventsPerDay: durationCompleted
465
514
  ? Math.round((accepted * 86_400_000) / elapsedMs)
466
515
  : null,
467
- estimatedStoredEventsPerDay: durationCompleted && reconciliationReliable
516
+ estimatedStoredEventsPerDay: durationCompleted && reconciliationPassed
468
517
  ? Math.round((storedEvents * 86_400_000) / elapsedMs)
469
518
  : null,
470
519
  certifiedEventsPerDay: certificationPassed
@@ -10,15 +10,16 @@
10
10
 
11
11
  import type * as affiliates from "../affiliates.js";
12
12
  import type * as constants from "../constants.js";
13
+ import type * as diagnostics from "../diagnostics.js";
13
14
  import type * as errors from "../errors.js";
14
15
  import type * as eventStore from "../eventStore.js";
15
16
  import type * as funnels from "../funnels.js";
17
+ import type * as geo from "../geo.js";
16
18
  import type * as goals from "../goals.js";
17
19
  import type * as guards from "../guards.js";
18
20
  import type * as http from "../http.js";
19
21
  import type * as ingest from "../ingest.js";
20
22
  import type * as live from "../live.js";
21
- import type * as migrations from "../migrations.js";
22
23
  import type * as reports from "../reports.js";
23
24
  import type * as retention from "../retention.js";
24
25
  import type * as sanitize from "../sanitize.js";
@@ -35,15 +36,16 @@ import { anyApi, componentsGeneric } from "convex/server";
35
36
  const fullApi: ApiFromModules<{
36
37
  affiliates: typeof affiliates;
37
38
  constants: typeof constants;
39
+ diagnostics: typeof diagnostics;
38
40
  errors: typeof errors;
39
41
  eventStore: typeof eventStore;
40
42
  funnels: typeof funnels;
43
+ geo: typeof geo;
41
44
  goals: typeof goals;
42
45
  guards: typeof guards;
43
46
  http: typeof http;
44
47
  ingest: typeof ingest;
45
48
  live: typeof live;
46
- migrations: typeof migrations;
47
49
  reports: typeof reports;
48
50
  retention: typeof retention;
49
51
  sanitize: typeof sanitize;
@@ -77,6 +79,4 @@ export const internal: FilterApi<
77
79
  FunctionReference<any, "internal">
78
80
  > = anyApi as any;
79
81
 
80
- export const components = componentsGeneric() as unknown as {
81
- migrations: import("@convex-dev/migrations/_generated/component.js").ComponentApi<"migrations">;
82
- };
82
+ export const components = componentsGeneric() as unknown as {};
@@ -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",
@@ -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
  /**
@@ -1,7 +1,13 @@
1
- import migrations from "@convex-dev/migrations/convex.config.js";
2
1
  import { defineComponent } from "convex/server";
2
+ import { v } from "convex/values";
3
3
 
4
- const component = defineComponent("rastroAnalytics");
5
- component.use(migrations);
6
-
7
- export default component;
4
+ export default defineComponent("rastroAnalytics", {
5
+ env: {
6
+ RASTRO_GEOIP_PROVIDER: v.optional(
7
+ v.union(v.literal("ipinfo"), v.literal("ipwhois")),
8
+ ),
9
+ RASTRO_GEOIP_TOKEN: v.optional(v.string()),
10
+ RASTRO_GEOIP_DAILY_LIMIT: v.optional(v.string()),
11
+ RASTRO_TRUST_PROXY: v.optional(v.literal("true")),
12
+ },
13
+ });
@@ -0,0 +1,64 @@
1
+ import { v } from "convex/values";
2
+ import { query } from "./_generated/server.js";
3
+ import { fail } from "./errors.js";
4
+ import { sanitizeOpaqueId } from "./sanitize.js";
5
+
6
+ const MAX_SESSION_TOTAL_IDS = 100;
7
+
8
+ export const sessionEventTotals = query({
9
+ args: {
10
+ siteId: v.id("sites"),
11
+ sessionIds: v.array(v.string()),
12
+ },
13
+ returns: v.object({
14
+ requestedSessions: v.number(),
15
+ foundSessions: v.number(),
16
+ storedEvents: v.number(),
17
+ }),
18
+ handler: async (ctx, args) => {
19
+ if (
20
+ args.sessionIds.length < 1 ||
21
+ args.sessionIds.length > MAX_SESSION_TOTAL_IDS
22
+ ) {
23
+ fail(
24
+ "INVALID_ARGUMENT",
25
+ `sessionIds must contain between 1 and ${MAX_SESSION_TOTAL_IDS} IDs`,
26
+ );
27
+ }
28
+ let sessionIds: string[];
29
+ try {
30
+ sessionIds = args.sessionIds.map((sessionId) => {
31
+ const sanitized = sanitizeOpaqueId(sessionId, "sessionId");
32
+ if (sanitized !== sessionId) throw new Error("sessionId is not canonical");
33
+ return sanitized;
34
+ });
35
+ } catch (error) {
36
+ fail(
37
+ "INVALID_ARGUMENT",
38
+ error instanceof Error ? error.message : "invalid sessionId",
39
+ );
40
+ }
41
+ if (new Set(sessionIds).size !== sessionIds.length) {
42
+ fail("INVALID_ARGUMENT", "sessionIds must be unique");
43
+ }
44
+
45
+ const sessions = await Promise.all(
46
+ sessionIds.map((sessionId) =>
47
+ ctx.db
48
+ .query("sessions")
49
+ .withIndex("by_siteId_and_sessionId", (range) =>
50
+ range.eq("siteId", args.siteId).eq("sessionId", sessionId),
51
+ )
52
+ .unique()
53
+ ),
54
+ );
55
+ return {
56
+ requestedSessions: sessionIds.length,
57
+ foundSessions: sessions.filter((session) => session !== null).length,
58
+ storedEvents: sessions.reduce(
59
+ (total, session) => total + (session?.eventCount ?? 0),
60
+ 0,
61
+ ),
62
+ };
63
+ },
64
+ });