@iann29/rastro 0.2.0 → 0.3.0

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.
@@ -932,6 +932,102 @@ async function readRouteRollups(
932
932
  return { rows, completeFrom: cutAt + DAY_MS };
933
933
  }
934
934
 
935
+ /** Metadata for a selected session, independent of list pagination. */
936
+ export const getSession = query({
937
+ args: { siteId: v.id("sites"), sessionId: v.string() },
938
+ returns: v.union(sessionDocumentValidator, v.null()),
939
+ handler: async (ctx, args) => {
940
+ const session = await ctx.db
941
+ .query("sessions")
942
+ .withIndex("by_siteId_and_sessionId", (q) =>
943
+ q.eq("siteId", args.siteId).eq("sessionId", args.sessionId),
944
+ )
945
+ .unique();
946
+ if (!session) return null;
947
+ const {
948
+ geoLookupAttemptedAt: _geo,
949
+ lastPageviewAt: _pageview,
950
+ ...result
951
+ } = session;
952
+ return result;
953
+ },
954
+ });
955
+
956
+ /** Exact commissions alongside overview's period revenue/conversion totals. */
957
+ export const revenueSummary = query({
958
+ args: { siteIds: v.array(v.id("sites")), from: v.number(), to: v.number() },
959
+ returns: v.object({ commissionCents: v.number() }),
960
+ handler: async (ctx, args) => {
961
+ const siteIds = validateSiteIds(args.siteIds);
962
+ validateRange(args.from, args.to, MAX_REPORT_RANGE_DAYS);
963
+ const fullFrom = Math.ceil(args.from / DAY_MS) * DAY_MS;
964
+ const fullTo = Math.floor((args.to + 1) / DAY_MS) * DAY_MS;
965
+ let commissionCents = 0;
966
+ let rowsRead = 0;
967
+ const currencies = new Set<string>();
968
+ for (const siteId of siteIds) {
969
+ const site = await ctx.db.get("sites", siteId);
970
+ if (!site) fail("NOT_FOUND", "site not found");
971
+ currencies.add(site.currency ?? "USD");
972
+ if (currencies.size > 1)
973
+ fail("CURRENCY_MISMATCH", "revenue sites must use the same currency");
974
+ if (fullFrom < fullTo) {
975
+ await assertSourceAvailable(
976
+ ctx,
977
+ [siteId],
978
+ ["affiliateStats"],
979
+ fullFrom,
980
+ );
981
+ const stats = await ctx.db
982
+ .query("affiliateDailyStats")
983
+ .withIndex("by_siteId_and_bucketStart", (q) =>
984
+ q
985
+ .eq("siteId", siteId)
986
+ .gte("bucketStart", fullFrom)
987
+ .lt("bucketStart", fullTo),
988
+ )
989
+ .take(MAX_REPORT_BUCKETS - rowsRead + 1);
990
+ rowsRead += stats.length;
991
+ failIfIncomplete(
992
+ stats,
993
+ MAX_REPORT_BUCKETS - (rowsRead - stats.length),
994
+ "commission buckets",
995
+ );
996
+ for (const row of stats) commissionCents += row.commissionCents;
997
+ }
998
+ // ponytail: boundary scans cap at MAX_REPORT_BUCKETS; add hourly commission
999
+ // rollups if busy partial days exceed this explicit report limit.
1000
+ // Daily stats cannot answer partial days (24h and local-day windows).
1001
+ // Read only those edges from the trusted ledger, never round the period.
1002
+ const edges: Array<[number, number]> =
1003
+ fullFrom < fullTo
1004
+ ? [
1005
+ [args.from, fullFrom - 1],
1006
+ [fullTo, args.to],
1007
+ ]
1008
+ : [[args.from, args.to]];
1009
+ for (const [from, to] of edges) {
1010
+ if (from > to) continue;
1011
+ await assertSourceAvailable(ctx, [siteId], ["conversions"], from);
1012
+ const rows = await ctx.db
1013
+ .query("conversions")
1014
+ .withIndex("by_siteId_and_timestamp", (q) =>
1015
+ q.eq("siteId", siteId).gte("timestamp", from).lte("timestamp", to),
1016
+ )
1017
+ .take(MAX_REPORT_BUCKETS - rowsRead + 1);
1018
+ rowsRead += rows.length;
1019
+ failIfIncomplete(
1020
+ rows,
1021
+ MAX_REPORT_BUCKETS - (rowsRead - rows.length),
1022
+ "commission boundary conversions",
1023
+ );
1024
+ for (const row of rows) commissionCents += row.commissionCents;
1025
+ }
1026
+ }
1027
+ return { commissionCents };
1028
+ },
1029
+ });
1030
+
935
1031
  export const listSessions = query({
936
1032
  args: {
937
1033
  siteId: v.id("sites"),
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/build-tracker.mjs. Do not edit.
2
- export const RASTRO_VERSION = "0.2.0";
2
+ export const RASTRO_VERSION = "0.3.0";
3
3
  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 o=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search),r=n.get(\"ref\")?.slice(0,64);let a,i,c;for(const e of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const t=n.get(\"utm_\"+e)?.slice(0,64);t&&((a??={})[\"utm_\"+e]=t)}try{i=sessionStorage._r||o(),sessionStorage._r=i,c=r||sessionStorage._a,c&&(sessionStorage._a=c),a||=sessionStorage._u&&JSON.parse(sessionStorage._u),a&&(sessionStorage._u=JSON.stringify(a))}catch{i=o(),c=r}let d=e.dataset.visitor;try{d||=localStorage._rv||=o()}catch{}d||=i;let l,p=0;const g=[],h=document.referrer?new URL(document.referrer).origin:void 0,u=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o,sentAt:Date.now()})]);let r=n();for(;r.size>64e3&&o.length>1;)g.unshift(o.pop()),r=n();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),g.length&&u(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${i}.${o.toString(36)}.${p}`,sessionId:i,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(u,1200)};let v;const f=()=>{const e=location.pathname;e!==v&&(v=e,m(\"pageview\",void 0,a&&{properties:a}))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:i,visitorId:d};\"event\"===e&&m(\"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,o=t.href,n=o&&new URL(o),r=/^https?:$/.test(n?.protocol),a=r&&n.origin!==location.origin;m(s?\"custom\":a?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((a?n.origin:\"\")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),f()}};S(\"pushState\"),S(\"replaceState\"),addEventListener(\"popstate\",f),addEventListener(\"pagehide\",()=>{m(\"leave\"),u(1)}),addEventListener(\"pageshow\",e=>{e.persisted&&(m(\"heartbeat\"),u(1))}),setInterval(()=>{m(\"heartbeat\"),u()},2e4),f()})();";
4
4
  export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VV227cNhD9FZswBBKeMLbjGIUEZtEgfXDhJkE3eTLcmKZGEhGJVMnROtuV/r2QVuv1JXmTZsi5nDlnyLlQ7zbGu0gHqHJvugYdSdOFgI6WJtiWgBQuZK5JRyQZLSHExxZ0eeuto77HhYzBJInD+4Ovf19xhit0FBngaBeyClhktuCH1PeHUQSkLrhsm92rsRQT1i15GbTLffP16+WHheSi7//SVM1GLo4ZA6fmHEvUwVSfddBN5LU3mqx3Mk5WAUE5WSJxFrBgYiFjbQ3yE7g4F1mNdKDBgskKH/gMwYEvrln0XTDIgDWY265hwIxuWm1Lx4ARhsniHaEjdiNm9GiXqqPmGzvGZ9koSTjXi4XaDOJ6d+ZGkRgorDdWRYzRerckH3SJ8lvoe88FvDArC0aFvn/u0GCShL+wKiNA9/2L8F2S/Ln89FG2OkR8ca0ToH8SrVPTnUjButIWa66FGIwmU22sGqs1KgwjrLnCB3asbLTkQza2mfe9GmdU7/tZ9f14dQ4zjCfsNJoaWnUyc6NU1zdQ7ekZsMAQMCx2RHvhEdIHW1qXrrzND06gU/jAc69KGdt5Nm9PxMRIL2t0JVVPSekmUo5Z3tf+jl8/638ziuEyTwm2RE89RHT0O6UfNKF0/p6LQdxsuRaU42LiWhZktP/hu4tzfJMku8zvTjNRys7FyhbEvWx9y8VEYS4yTBKnV7bU5IOM6PL3qI13PEIQfV8gmYpH2DRIlc9T9vnT8guDO5+v0wDfEVtd2xWmp4OQE9B8kv0goJyTJ0nHUQzQKI5AEMUjtPa9ZKVsu1jxzdTuZZ7eHm3sII82XpJfTrDwNxditLTD7Y68l3lqYabBZZ7mQOsWUwSnG0wJWk1V+qDc8W90PIjn7O2FgN1Y0wrINhhJN+0E9r8dOoNpe3wMuihsbTXhsu7K1ICUMg4CTI06fLEN+o54LaBWEWn338Hp2cmJGKYBreahF+rJUnxRWoaHSq2ShK8UQsNZq0tcWbxnMJNNJ8mmDb7FQBZjqgchhuzeutzfy6AjBf8IZVvw7TL5QUwphTMBN78Ab8i2S3U6myQNZ6aL5BsGt0cb6vvZO9w+APjbiYA4y31P3Sh2tDvD88W28PRx1XEQAzzoSuf5H2PgKxsJHQbOTG3NdwZ7WZFCSTqUSAtpah8xEmca7joi7+B63Aevtt2/mmq8YVvl0VPJRUUPu2N7fEoMXtH0foBTfv/C+FEgr/+piNq4SI9eSxrTuoVsgydvfC1Aq5Akbt4Hh2o/z60la3hc7EBM9YL5ju5853KW7nqMsNk2lvLntV3pO6z7nqR1DsMX/EHjD+nyo25QPH4BYCw+DYvxFdjlThkTx3tiiSekn1fXOIZTMYOzVI/xrmwkH9bXeJPtPxWXUuLILJK6bes1n32AAgouhiFbcjbKeEmakAlYjs9jW2uDO8vLabe+jZMTip+6dYmVzZHBJJ2Gsxr1agzV8VMx/OpKrPz9lkIoWwxxdOZJwhvOKtSB7lDTHGMMEpEuHWFY6Zrv8jw9JwY4w/Ntn4KL7H/zTKaM3wgAAA==";
5
5
  export const TRACKER_RAW_BYTES = 2271;