@iann29/rastro 0.1.0-alpha.0 → 0.1.0-alpha.10
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.
- package/README.md +579 -57
- package/agent/integration.md +661 -0
- package/agent/manifest.json +187 -0
- package/agent/manifest.schema.json +411 -0
- package/dist/client/federation.d.ts +236 -0
- package/dist/client/federation.d.ts.map +1 -0
- package/dist/client/federation.js +196 -0
- package/dist/client/federation.js.map +1 -0
- package/dist/client/index.d.ts +2467 -18
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +461 -32
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +18 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +302 -4
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/server.d.ts +4 -0
- package/dist/component/_generated/server.d.ts.map +1 -1
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/affiliates.d.ts.map +1 -1
- package/dist/component/affiliates.js +6 -2
- package/dist/component/affiliates.js.map +1 -1
- package/dist/component/cardinality.d.ts +12 -0
- package/dist/component/cardinality.d.ts.map +1 -0
- package/dist/component/cardinality.js +94 -0
- package/dist/component/cardinality.js.map +1 -0
- package/dist/component/constants.d.ts +34 -7
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +47 -7
- package/dist/component/constants.js.map +1 -1
- package/dist/component/convex.config.d.ts +6 -1
- package/dist/component/convex.config.js +9 -1
- package/dist/component/convex.config.js.map +1 -1
- package/dist/component/coverage.d.ts +19 -0
- package/dist/component/coverage.d.ts.map +1 -0
- package/dist/component/coverage.js +30 -0
- package/dist/component/coverage.js.map +1 -0
- package/dist/component/diagnostics.d.ts +9 -0
- package/dist/component/diagnostics.d.ts.map +1 -0
- package/dist/component/diagnostics.js +47 -0
- package/dist/component/diagnostics.js.map +1 -0
- package/dist/component/errors.d.ts +1 -1
- package/dist/component/errors.d.ts.map +1 -1
- package/dist/component/errors.js.map +1 -1
- package/dist/component/eventStore.d.ts +106 -0
- package/dist/component/eventStore.d.ts.map +1 -0
- package/dist/component/eventStore.js +294 -0
- package/dist/component/eventStore.js.map +1 -0
- package/dist/component/funnels.d.ts.map +1 -1
- package/dist/component/funnels.js +5 -3
- package/dist/component/funnels.js.map +1 -1
- package/dist/component/geo.d.ts +71 -0
- package/dist/component/geo.d.ts.map +1 -0
- package/dist/component/geo.js +611 -0
- package/dist/component/geo.js.map +1 -0
- package/dist/component/goals.d.ts.map +1 -1
- package/dist/component/goals.js +25 -9
- package/dist/component/goals.js.map +1 -1
- package/dist/component/guards.d.ts.map +1 -1
- package/dist/component/guards.js.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +284 -53
- package/dist/component/http.js.map +1 -1
- package/dist/component/identity.d.ts +13 -0
- package/dist/component/identity.d.ts.map +1 -0
- package/dist/component/identity.js +58 -0
- package/dist/component/identity.js.map +1 -0
- package/dist/component/ingest.d.ts +3 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +1105 -243
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/live.d.ts +8 -0
- package/dist/component/live.d.ts.map +1 -1
- package/dist/component/live.js +74 -9
- package/dist/component/live.js.map +1 -1
- package/dist/component/reports.d.ts +273 -24
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +1320 -150
- package/dist/component/reports.js.map +1 -1
- package/dist/component/retention.d.ts +75 -1
- package/dist/component/retention.d.ts.map +1 -1
- package/dist/component/retention.js +541 -31
- package/dist/component/retention.js.map +1 -1
- package/dist/component/sanitize.d.ts +24 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +107 -19
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +462 -55
- package/dist/component/schema.js +195 -15
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +38 -25
- package/dist/component/sites.js.map +1 -1
- package/dist/component/useragent.d.ts +9 -0
- package/dist/component/useragent.d.ts.map +1 -0
- package/dist/component/useragent.js +152 -0
- package/dist/component/useragent.js.map +1 -0
- package/dist/component/validators.d.ts +101 -16
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +19 -2
- package/dist/component/validators.js.map +1 -1
- package/dist/component/visitors.d.ts +19 -0
- package/dist/component/visitors.d.ts.map +1 -0
- package/dist/component/visitors.js +86 -0
- package/dist/component/visitors.js.map +1 -0
- package/dist/component/vitals.d.ts +41 -0
- package/dist/component/vitals.d.ts.map +1 -0
- package/dist/component/vitals.js +115 -0
- package/dist/component/vitals.js.map +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/tracker/generated.d.ts +8 -4
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +8 -4
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.d.ts +1 -1
- package/dist/tracker/tracker.d.ts.map +1 -1
- package/dist/tracker/tracker.js +81 -33
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker/vitals.d.ts +10 -0
- package/dist/tracker/vitals.d.ts.map +1 -0
- package/dist/tracker/vitals.js +140 -0
- package/dist/tracker/vitals.js.map +1 -0
- package/dist/tracker.min.js +1 -1
- package/dist/vitals.min.js +1 -0
- package/docs/benchmarks/2026-08-20-realistic.md +171 -0
- package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
- package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
- package/docs/federation-setup.md +397 -0
- package/docs/federation.md +262 -0
- package/docs/upgrading.md +259 -0
- package/llms.txt +71 -0
- package/package.json +61 -11
- package/scripts/benchmark-ingest.mjs +601 -0
- package/scripts/generate-federation-keys.mjs +20 -0
- package/src/component/_generated/api.ts +18 -0
- package/src/component/_generated/component.ts +366 -4
- package/src/component/_generated/server.ts +4 -0
- package/src/component/affiliates.ts +20 -5
- package/src/component/cardinality.ts +117 -0
- package/src/component/constants.ts +47 -7
- package/src/component/convex.config.ts +11 -1
- package/src/component/coverage.ts +36 -0
- package/src/component/diagnostics.ts +65 -0
- package/src/component/errors.ts +2 -1
- package/src/component/eventStore.ts +489 -0
- package/src/component/funnels.ts +19 -16
- package/src/component/geo.ts +781 -0
- package/src/component/goals.ts +45 -16
- package/src/component/guards.ts +3 -1
- package/src/component/http.ts +403 -60
- package/src/component/identity.ts +74 -0
- package/src/component/ingest.ts +1652 -291
- package/src/component/live.ts +90 -9
- package/src/component/reports.ts +1929 -199
- package/src/component/retention.ts +774 -97
- package/src/component/sanitize.ts +154 -32
- package/src/component/schema.ts +219 -15
- package/src/component/sites.ts +56 -28
- package/src/component/useragent.ts +171 -0
- package/src/component/validators.ts +29 -1
- package/src/component/visitors.ts +116 -0
- 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
|
-
| "
|
|
210
|
+
| "leave"
|
|
211
|
+
| "outbound"
|
|
212
|
+
| "vital";
|
|
213
|
+
value?: number;
|
|
197
214
|
visitorId: string;
|
|
198
215
|
}>;
|
|
199
216
|
origin: string;
|
|
@@ -244,10 +261,61 @@ 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: "UTC";
|
|
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
|
+
| "events"
|
|
305
|
+
| "sessions"
|
|
306
|
+
| "conversions"
|
|
307
|
+
| "goals"
|
|
308
|
+
| "funnels"
|
|
309
|
+
| "affiliates"
|
|
310
|
+
| "vitals"
|
|
311
|
+
| "siteMap";
|
|
312
|
+
}>;
|
|
313
|
+
from: number;
|
|
314
|
+
siteId: string;
|
|
315
|
+
to: number;
|
|
316
|
+
},
|
|
317
|
+
Name
|
|
318
|
+
>;
|
|
251
319
|
funnelsReport: FunctionReference<
|
|
252
320
|
"query",
|
|
253
321
|
"internal",
|
|
@@ -259,6 +327,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
259
327
|
conversionWindowMs: number;
|
|
260
328
|
createdAt: number;
|
|
261
329
|
key: string;
|
|
330
|
+
metadata: {
|
|
331
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
332
|
+
coverage: {
|
|
333
|
+
availableFrom: number | null;
|
|
334
|
+
availableThrough: number | null;
|
|
335
|
+
generation: number | null;
|
|
336
|
+
retainedBefore: number | null;
|
|
337
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
338
|
+
};
|
|
339
|
+
range: {
|
|
340
|
+
boundary: "inclusive";
|
|
341
|
+
from: number;
|
|
342
|
+
timezone: "UTC";
|
|
343
|
+
to: number;
|
|
344
|
+
};
|
|
345
|
+
};
|
|
262
346
|
name: string;
|
|
263
347
|
siteId: string;
|
|
264
348
|
steps: Array<{
|
|
@@ -296,6 +380,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
296
380
|
updatedAt: number;
|
|
297
381
|
valueCents: number;
|
|
298
382
|
}>;
|
|
383
|
+
metadata: {
|
|
384
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
385
|
+
coverage: {
|
|
386
|
+
availableFrom: number | null;
|
|
387
|
+
availableThrough: number | null;
|
|
388
|
+
generation: number | null;
|
|
389
|
+
retainedBefore: number | null;
|
|
390
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
391
|
+
};
|
|
392
|
+
range: {
|
|
393
|
+
boundary: "inclusive";
|
|
394
|
+
from: number;
|
|
395
|
+
timezone: "UTC";
|
|
396
|
+
to: number;
|
|
397
|
+
};
|
|
398
|
+
};
|
|
299
399
|
truncated: boolean;
|
|
300
400
|
},
|
|
301
401
|
Name
|
|
@@ -394,6 +494,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
394
494
|
utmMedium?: string;
|
|
395
495
|
utmSource?: string;
|
|
396
496
|
visitorId: string;
|
|
497
|
+
visitorKey?: string;
|
|
397
498
|
}>;
|
|
398
499
|
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
399
500
|
splitCursor?: string | null;
|
|
@@ -416,9 +517,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
416
517
|
lastSeenAt: number;
|
|
417
518
|
latitude?: number;
|
|
418
519
|
longitude?: number;
|
|
520
|
+
movedAt?: number;
|
|
419
521
|
os: string;
|
|
420
522
|
pageviewCount: number;
|
|
421
523
|
path: string;
|
|
524
|
+
previousPath?: string;
|
|
422
525
|
sessionId: string;
|
|
423
526
|
siteId: string;
|
|
424
527
|
source: string;
|
|
@@ -440,6 +543,44 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
440
543
|
currency: string;
|
|
441
544
|
from: number;
|
|
442
545
|
interval: "hour" | "day";
|
|
546
|
+
metadata: {
|
|
547
|
+
breakdowns: {
|
|
548
|
+
topBrowsers: "eventVolume";
|
|
549
|
+
topCampaigns: "eventVolume";
|
|
550
|
+
topCountries: "eventVolume";
|
|
551
|
+
topDevices: "eventVolume";
|
|
552
|
+
topEvents: "eventVolume";
|
|
553
|
+
topGoals: "goalCompletionVolume";
|
|
554
|
+
topPages: "eventVolume";
|
|
555
|
+
topSources: "eventVolume";
|
|
556
|
+
};
|
|
557
|
+
coverage: {
|
|
558
|
+
availableFrom: number | null;
|
|
559
|
+
availableThrough: number | null;
|
|
560
|
+
generation: number | null;
|
|
561
|
+
retainedBefore: number | null;
|
|
562
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
563
|
+
};
|
|
564
|
+
features: {
|
|
565
|
+
botDetection: "userAgent";
|
|
566
|
+
errorInsights: "unsupported";
|
|
567
|
+
outboundLinks: "eventVolume";
|
|
568
|
+
sessionReplay: "unsupported";
|
|
569
|
+
};
|
|
570
|
+
range: {
|
|
571
|
+
boundary: "inclusive";
|
|
572
|
+
from: number;
|
|
573
|
+
timezone: "UTC";
|
|
574
|
+
to: number;
|
|
575
|
+
};
|
|
576
|
+
visitors: {
|
|
577
|
+
basis: "estimatedUnique" | "bucketOccurrences";
|
|
578
|
+
exact: boolean;
|
|
579
|
+
legacyIncomplete: boolean;
|
|
580
|
+
precision: number | null;
|
|
581
|
+
sketchVersion: number | null;
|
|
582
|
+
};
|
|
583
|
+
};
|
|
443
584
|
timeSeries: Array<{
|
|
444
585
|
clicks: number;
|
|
445
586
|
conversions: number;
|
|
@@ -458,6 +599,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
458
599
|
revenueCents: number;
|
|
459
600
|
value: string;
|
|
460
601
|
}>;
|
|
602
|
+
topCampaigns: Array<{
|
|
603
|
+
count: number;
|
|
604
|
+
revenueCents: number;
|
|
605
|
+
value: string;
|
|
606
|
+
}>;
|
|
461
607
|
topCountries: Array<{
|
|
462
608
|
count: number;
|
|
463
609
|
revenueCents: number;
|
|
@@ -552,7 +698,9 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
552
698
|
| "custom"
|
|
553
699
|
| "conversion"
|
|
554
700
|
| "heartbeat"
|
|
555
|
-
| "
|
|
701
|
+
| "leave"
|
|
702
|
+
| "outbound"
|
|
703
|
+
| "vital";
|
|
556
704
|
visitorId: string;
|
|
557
705
|
}>;
|
|
558
706
|
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
@@ -560,6 +708,54 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
560
708
|
},
|
|
561
709
|
Name
|
|
562
710
|
>;
|
|
711
|
+
siteMap: FunctionReference<
|
|
712
|
+
"query",
|
|
713
|
+
"internal",
|
|
714
|
+
{ from: number; siteIds: Array<string>; to: number },
|
|
715
|
+
{
|
|
716
|
+
from: number;
|
|
717
|
+
metadata: {
|
|
718
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
719
|
+
coverage: {
|
|
720
|
+
availableFrom: number | null;
|
|
721
|
+
availableThrough: number | null;
|
|
722
|
+
generation: number | null;
|
|
723
|
+
retainedBefore: number | null;
|
|
724
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
725
|
+
};
|
|
726
|
+
limits: {
|
|
727
|
+
routesPerDay: number;
|
|
728
|
+
rows: number;
|
|
729
|
+
transitionSlots: number;
|
|
730
|
+
};
|
|
731
|
+
range: {
|
|
732
|
+
boundary: "inclusive";
|
|
733
|
+
from: number;
|
|
734
|
+
timezone: "UTC";
|
|
735
|
+
to: number;
|
|
736
|
+
};
|
|
737
|
+
visitors: { basis: "estimatedUnique"; exact: boolean };
|
|
738
|
+
};
|
|
739
|
+
routes: Array<{
|
|
740
|
+
bounces: number;
|
|
741
|
+
daily: Array<{ bucketStart: number; pageviews: number }>;
|
|
742
|
+
dwellMs: number;
|
|
743
|
+
dwellSamples: number;
|
|
744
|
+
entries: number;
|
|
745
|
+
exits: number;
|
|
746
|
+
pageviews: number;
|
|
747
|
+
route: string;
|
|
748
|
+
transitionsIn: number;
|
|
749
|
+
transitionsOut: number;
|
|
750
|
+
visitors: number;
|
|
751
|
+
}>;
|
|
752
|
+
to: number;
|
|
753
|
+
transitions: Array<{ count: number; from: string; to: string }>;
|
|
754
|
+
truncated: { routes: boolean; transitions: boolean };
|
|
755
|
+
window: { from: number; to: number } | null;
|
|
756
|
+
},
|
|
757
|
+
Name
|
|
758
|
+
>;
|
|
563
759
|
visitorJourney: FunctionReference<
|
|
564
760
|
"query",
|
|
565
761
|
"internal",
|
|
@@ -600,11 +796,70 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
600
796
|
| "custom"
|
|
601
797
|
| "conversion"
|
|
602
798
|
| "heartbeat"
|
|
603
|
-
| "
|
|
799
|
+
| "leave"
|
|
800
|
+
| "outbound"
|
|
801
|
+
| "vital";
|
|
604
802
|
visitorId: string;
|
|
605
803
|
}>,
|
|
606
804
|
Name
|
|
607
805
|
>;
|
|
806
|
+
vitalsReport: FunctionReference<
|
|
807
|
+
"query",
|
|
808
|
+
"internal",
|
|
809
|
+
{ from: number; siteId: string; to: number },
|
|
810
|
+
{
|
|
811
|
+
metadata: {
|
|
812
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
813
|
+
coverage: {
|
|
814
|
+
availableFrom: number | null;
|
|
815
|
+
availableThrough: number | null;
|
|
816
|
+
generation: number | null;
|
|
817
|
+
retainedBefore: number | null;
|
|
818
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
819
|
+
};
|
|
820
|
+
range: {
|
|
821
|
+
boundary: "inclusive";
|
|
822
|
+
from: number;
|
|
823
|
+
timezone: "UTC";
|
|
824
|
+
to: number;
|
|
825
|
+
};
|
|
826
|
+
};
|
|
827
|
+
metrics: Array<{
|
|
828
|
+
average: number;
|
|
829
|
+
count: number;
|
|
830
|
+
devices: Array<{
|
|
831
|
+
average: number;
|
|
832
|
+
count: number;
|
|
833
|
+
device: string;
|
|
834
|
+
good: number;
|
|
835
|
+
needsImprovement: number;
|
|
836
|
+
p75: number;
|
|
837
|
+
poor: number;
|
|
838
|
+
}>;
|
|
839
|
+
good: number;
|
|
840
|
+
metric: "LCP" | "CLS" | "INP" | "FCP" | "TTFB";
|
|
841
|
+
needsImprovement: number;
|
|
842
|
+
p75: number;
|
|
843
|
+
pages: Array<{
|
|
844
|
+
average: number;
|
|
845
|
+
count: number;
|
|
846
|
+
good: number;
|
|
847
|
+
needsImprovement: number;
|
|
848
|
+
p75: number;
|
|
849
|
+
page: string;
|
|
850
|
+
poor: number;
|
|
851
|
+
}>;
|
|
852
|
+
poor: number;
|
|
853
|
+
timeSeries: Array<{
|
|
854
|
+
count: number;
|
|
855
|
+
p75: number;
|
|
856
|
+
timestamp: number;
|
|
857
|
+
}>;
|
|
858
|
+
}>;
|
|
859
|
+
truncated: boolean;
|
|
860
|
+
},
|
|
861
|
+
Name
|
|
862
|
+
>;
|
|
608
863
|
};
|
|
609
864
|
retention: {
|
|
610
865
|
cleanup: FunctionReference<
|
|
@@ -623,13 +878,111 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
623
878
|
| "funnelProgress"
|
|
624
879
|
| "funnelSteps"
|
|
625
880
|
| "affiliateStats"
|
|
881
|
+
| "goalRollups"
|
|
882
|
+
| "funnelRollups"
|
|
883
|
+
| "affiliateRollups"
|
|
884
|
+
| "vitalRollups"
|
|
885
|
+
| "siteMapRollups"
|
|
886
|
+
| "attributions"
|
|
887
|
+
| "liveSessions"
|
|
888
|
+
| "ingestWindows"
|
|
889
|
+
| "siteIngestWindows";
|
|
890
|
+
siteId: string;
|
|
891
|
+
},
|
|
892
|
+
{ before: number; deleted: number; hasMore: boolean },
|
|
893
|
+
Name
|
|
894
|
+
>;
|
|
895
|
+
disablePolicy: FunctionReference<
|
|
896
|
+
"mutation",
|
|
897
|
+
"internal",
|
|
898
|
+
{ siteId: string },
|
|
899
|
+
null,
|
|
900
|
+
Name
|
|
901
|
+
>;
|
|
902
|
+
runPolicy: FunctionReference<
|
|
903
|
+
"mutation",
|
|
904
|
+
"internal",
|
|
905
|
+
{
|
|
906
|
+
batchSize?: number;
|
|
907
|
+
kind:
|
|
908
|
+
| "events"
|
|
909
|
+
| "sessions"
|
|
910
|
+
| "conversions"
|
|
911
|
+
| "goalCompletions"
|
|
912
|
+
| "aggregateHour"
|
|
913
|
+
| "aggregateDay"
|
|
914
|
+
| "funnelProgress"
|
|
915
|
+
| "funnelSteps"
|
|
916
|
+
| "affiliateStats"
|
|
917
|
+
| "goalRollups"
|
|
918
|
+
| "funnelRollups"
|
|
919
|
+
| "affiliateRollups"
|
|
920
|
+
| "vitalRollups"
|
|
921
|
+
| "siteMapRollups"
|
|
626
922
|
| "attributions"
|
|
627
923
|
| "liveSessions"
|
|
628
924
|
| "ingestWindows"
|
|
629
925
|
| "siteIngestWindows";
|
|
630
926
|
siteId: string;
|
|
631
927
|
},
|
|
632
|
-
{ deleted: number; hasMore: boolean },
|
|
928
|
+
{ before: number; deleted: number; hasMore: boolean },
|
|
929
|
+
Name
|
|
930
|
+
>;
|
|
931
|
+
setPolicy: FunctionReference<
|
|
932
|
+
"mutation",
|
|
933
|
+
"internal",
|
|
934
|
+
{
|
|
935
|
+
aggregatesDays: number;
|
|
936
|
+
conversionsDays: number;
|
|
937
|
+
eventsDays: number;
|
|
938
|
+
reportsDays: number;
|
|
939
|
+
sessionsDays: number;
|
|
940
|
+
siteId: string;
|
|
941
|
+
},
|
|
942
|
+
null,
|
|
943
|
+
Name
|
|
944
|
+
>;
|
|
945
|
+
status: FunctionReference<
|
|
946
|
+
"query",
|
|
947
|
+
"internal",
|
|
948
|
+
{ siteId: string },
|
|
949
|
+
{
|
|
950
|
+
backlog: number;
|
|
951
|
+
jobs: Array<{
|
|
952
|
+
before: number;
|
|
953
|
+
deleted: number;
|
|
954
|
+
kind:
|
|
955
|
+
| "events"
|
|
956
|
+
| "sessions"
|
|
957
|
+
| "conversions"
|
|
958
|
+
| "goalCompletions"
|
|
959
|
+
| "aggregateHour"
|
|
960
|
+
| "aggregateDay"
|
|
961
|
+
| "funnelProgress"
|
|
962
|
+
| "funnelSteps"
|
|
963
|
+
| "affiliateStats"
|
|
964
|
+
| "goalRollups"
|
|
965
|
+
| "funnelRollups"
|
|
966
|
+
| "affiliateRollups"
|
|
967
|
+
| "vitalRollups"
|
|
968
|
+
| "siteMapRollups"
|
|
969
|
+
| "attributions"
|
|
970
|
+
| "liveSessions"
|
|
971
|
+
| "ingestWindows"
|
|
972
|
+
| "siteIngestWindows";
|
|
973
|
+
scheduledAt: number | null;
|
|
974
|
+
status: "pending" | "running" | "complete";
|
|
975
|
+
updatedAt: number;
|
|
976
|
+
}>;
|
|
977
|
+
policy: {
|
|
978
|
+
aggregatesDays: number;
|
|
979
|
+
conversionsDays: number;
|
|
980
|
+
eventsDays: number;
|
|
981
|
+
reportsDays: number;
|
|
982
|
+
sessionsDays: number;
|
|
983
|
+
updatedAt: number;
|
|
984
|
+
} | null;
|
|
985
|
+
},
|
|
633
986
|
Name
|
|
634
987
|
>;
|
|
635
988
|
};
|
|
@@ -703,4 +1056,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
703
1056
|
Name
|
|
704
1057
|
>;
|
|
705
1058
|
};
|
|
1059
|
+
visitors: {
|
|
1060
|
+
link: FunctionReference<
|
|
1061
|
+
"mutation",
|
|
1062
|
+
"internal",
|
|
1063
|
+
{ previousVisitorId: string; siteId: string; visitorId: string },
|
|
1064
|
+
{ aliasCount: number; linked: boolean },
|
|
1065
|
+
Name
|
|
1066
|
+
>;
|
|
1067
|
+
};
|
|
706
1068
|
};
|
|
@@ -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)))
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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 (
|
|
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
|
+
}
|