@iann29/rastro 0.1.0-alpha.3 → 0.1.0-alpha.5
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 +374 -62
- package/agent/integration.md +651 -0
- package/agent/manifest.json +183 -0
- package/agent/manifest.schema.json +405 -0
- package/dist/client/federation.d.ts +205 -0
- package/dist/client/federation.d.ts.map +1 -0
- package/dist/client/federation.js +179 -0
- package/dist/client/federation.js.map +1 -0
- package/dist/client/index.d.ts +1658 -8
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +228 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +10 -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 +169 -1
- package/dist/component/_generated/component.d.ts.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 +11 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +11 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/coverage.d.ts +18 -0
- package/dist/component/coverage.d.ts.map +1 -0
- package/dist/component/coverage.js +19 -0
- package/dist/component/coverage.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 +1 -0
- package/dist/component/eventStore.d.ts.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +85 -36
- 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 +1 -0
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +258 -19
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/live.d.ts.map +1 -1
- package/dist/component/live.js +6 -3
- package/dist/component/live.js.map +1 -1
- package/dist/component/reports.d.ts +120 -2
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +659 -91
- 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 +517 -54
- package/dist/component/retention.js.map +1 -1
- package/dist/component/sanitize.d.ts +4 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +13 -5
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +224 -7
- package/dist/component/schema.js +113 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +5 -1
- 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 +42 -11
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +10 -1
- 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/tracker/generated.d.ts +4 -4
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +4 -4
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.js +23 -11
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/benchmarks/2026-08-20-realistic.md +4 -4
- package/docs/benchmarks/2026-08-21-formal-certification.md +333 -0
- package/docs/federation-setup.md +395 -0
- package/docs/federation.md +258 -0
- package/docs/upgrading.md +130 -0
- package/llms.txt +65 -0
- package/package.json +41 -11
- package/scripts/generate-federation-keys.mjs +20 -0
- package/src/component/_generated/api.ts +10 -0
- package/src/component/_generated/component.ts +228 -1
- package/src/component/cardinality.ts +116 -0
- package/src/component/constants.ts +11 -0
- package/src/component/coverage.ts +25 -0
- package/src/component/errors.ts +2 -1
- package/src/component/http.ts +129 -38
- package/src/component/identity.ts +74 -0
- package/src/component/ingest.ts +414 -13
- package/src/component/live.ts +8 -3
- package/src/component/reports.ts +910 -98
- package/src/component/retention.ts +624 -88
- package/src/component/sanitize.ts +19 -5
- package/src/component/schema.ts +125 -0
- package/src/component/sites.ts +5 -1
- package/src/component/useragent.ts +171 -0
- package/src/component/validators.ts +11 -0
- package/src/component/visitors.ts +105 -0
|
@@ -185,6 +185,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
185
185
|
latitude?: number;
|
|
186
186
|
longitude?: number;
|
|
187
187
|
os?: string;
|
|
188
|
+
visitorKey?: string;
|
|
188
189
|
};
|
|
189
190
|
events: Array<{
|
|
190
191
|
affiliateSlug?: string;
|
|
@@ -257,10 +258,59 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
257
258
|
updatedAt: number;
|
|
258
259
|
visits: number;
|
|
259
260
|
}>;
|
|
261
|
+
metadata: {
|
|
262
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
263
|
+
coverage: {
|
|
264
|
+
availableFrom: number | null;
|
|
265
|
+
availableThrough: number | null;
|
|
266
|
+
generation: number | null;
|
|
267
|
+
retainedBefore: number | null;
|
|
268
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
269
|
+
};
|
|
270
|
+
range: {
|
|
271
|
+
boundary: "inclusive";
|
|
272
|
+
from: number;
|
|
273
|
+
timezone: "UTC";
|
|
274
|
+
to: number;
|
|
275
|
+
};
|
|
276
|
+
uniqueVisitors: {
|
|
277
|
+
basis: "estimatedUnique" | "exactLegacy";
|
|
278
|
+
exact: boolean;
|
|
279
|
+
};
|
|
280
|
+
};
|
|
260
281
|
truncated: boolean;
|
|
261
282
|
},
|
|
262
283
|
Name
|
|
263
284
|
>;
|
|
285
|
+
dataCoverage: FunctionReference<
|
|
286
|
+
"query",
|
|
287
|
+
"internal",
|
|
288
|
+
{ from: number; siteId: string; to: number },
|
|
289
|
+
{
|
|
290
|
+
datasets: Array<{
|
|
291
|
+
coverage: {
|
|
292
|
+
availableFrom: number | null;
|
|
293
|
+
availableThrough: number | null;
|
|
294
|
+
generation: number | null;
|
|
295
|
+
retainedBefore: number | null;
|
|
296
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
297
|
+
};
|
|
298
|
+
dataset:
|
|
299
|
+
| "overviewHour"
|
|
300
|
+
| "overviewDay"
|
|
301
|
+
| "events"
|
|
302
|
+
| "sessions"
|
|
303
|
+
| "conversions"
|
|
304
|
+
| "goals"
|
|
305
|
+
| "funnels"
|
|
306
|
+
| "affiliates";
|
|
307
|
+
}>;
|
|
308
|
+
from: number;
|
|
309
|
+
siteId: string;
|
|
310
|
+
to: number;
|
|
311
|
+
},
|
|
312
|
+
Name
|
|
313
|
+
>;
|
|
264
314
|
funnelsReport: FunctionReference<
|
|
265
315
|
"query",
|
|
266
316
|
"internal",
|
|
@@ -272,6 +322,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
272
322
|
conversionWindowMs: number;
|
|
273
323
|
createdAt: number;
|
|
274
324
|
key: string;
|
|
325
|
+
metadata: {
|
|
326
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
327
|
+
coverage: {
|
|
328
|
+
availableFrom: number | null;
|
|
329
|
+
availableThrough: number | null;
|
|
330
|
+
generation: number | null;
|
|
331
|
+
retainedBefore: number | null;
|
|
332
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
333
|
+
};
|
|
334
|
+
range: {
|
|
335
|
+
boundary: "inclusive";
|
|
336
|
+
from: number;
|
|
337
|
+
timezone: "UTC";
|
|
338
|
+
to: number;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
275
341
|
name: string;
|
|
276
342
|
siteId: string;
|
|
277
343
|
steps: Array<{
|
|
@@ -309,6 +375,22 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
309
375
|
updatedAt: number;
|
|
310
376
|
valueCents: number;
|
|
311
377
|
}>;
|
|
378
|
+
metadata: {
|
|
379
|
+
basis: "dailyRollup" | "boundedLegacy";
|
|
380
|
+
coverage: {
|
|
381
|
+
availableFrom: number | null;
|
|
382
|
+
availableThrough: number | null;
|
|
383
|
+
generation: number | null;
|
|
384
|
+
retainedBefore: number | null;
|
|
385
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
386
|
+
};
|
|
387
|
+
range: {
|
|
388
|
+
boundary: "inclusive";
|
|
389
|
+
from: number;
|
|
390
|
+
timezone: "UTC";
|
|
391
|
+
to: number;
|
|
392
|
+
};
|
|
393
|
+
};
|
|
312
394
|
truncated: boolean;
|
|
313
395
|
},
|
|
314
396
|
Name
|
|
@@ -407,6 +489,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
407
489
|
utmMedium?: string;
|
|
408
490
|
utmSource?: string;
|
|
409
491
|
visitorId: string;
|
|
492
|
+
visitorKey?: string;
|
|
410
493
|
}>;
|
|
411
494
|
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
412
495
|
splitCursor?: string | null;
|
|
@@ -453,6 +536,44 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
453
536
|
currency: string;
|
|
454
537
|
from: number;
|
|
455
538
|
interval: "hour" | "day";
|
|
539
|
+
metadata: {
|
|
540
|
+
breakdowns: {
|
|
541
|
+
topBrowsers: "eventVolume";
|
|
542
|
+
topCampaigns: "eventVolume";
|
|
543
|
+
topCountries: "eventVolume";
|
|
544
|
+
topDevices: "eventVolume";
|
|
545
|
+
topEvents: "eventVolume";
|
|
546
|
+
topGoals: "goalCompletionVolume";
|
|
547
|
+
topPages: "eventVolume";
|
|
548
|
+
topSources: "eventVolume";
|
|
549
|
+
};
|
|
550
|
+
coverage: {
|
|
551
|
+
availableFrom: number | null;
|
|
552
|
+
availableThrough: number | null;
|
|
553
|
+
generation: number | null;
|
|
554
|
+
retainedBefore: number | null;
|
|
555
|
+
state: "complete" | "partial" | "retained" | "unavailable";
|
|
556
|
+
};
|
|
557
|
+
features: {
|
|
558
|
+
botDetection: "userAgent";
|
|
559
|
+
errorInsights: "unsupported";
|
|
560
|
+
outboundLinks: "eventVolume";
|
|
561
|
+
sessionReplay: "unsupported";
|
|
562
|
+
};
|
|
563
|
+
range: {
|
|
564
|
+
boundary: "inclusive";
|
|
565
|
+
from: number;
|
|
566
|
+
timezone: "UTC";
|
|
567
|
+
to: number;
|
|
568
|
+
};
|
|
569
|
+
visitors: {
|
|
570
|
+
basis: "estimatedUnique" | "bucketOccurrences";
|
|
571
|
+
exact: boolean;
|
|
572
|
+
legacyIncomplete: boolean;
|
|
573
|
+
precision: number | null;
|
|
574
|
+
sketchVersion: number | null;
|
|
575
|
+
};
|
|
576
|
+
};
|
|
456
577
|
timeSeries: Array<{
|
|
457
578
|
clicks: number;
|
|
458
579
|
conversions: number;
|
|
@@ -471,6 +592,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
471
592
|
revenueCents: number;
|
|
472
593
|
value: string;
|
|
473
594
|
}>;
|
|
595
|
+
topCampaigns: Array<{
|
|
596
|
+
count: number;
|
|
597
|
+
revenueCents: number;
|
|
598
|
+
value: string;
|
|
599
|
+
}>;
|
|
474
600
|
topCountries: Array<{
|
|
475
601
|
count: number;
|
|
476
602
|
revenueCents: number;
|
|
@@ -636,13 +762,105 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
636
762
|
| "funnelProgress"
|
|
637
763
|
| "funnelSteps"
|
|
638
764
|
| "affiliateStats"
|
|
765
|
+
| "goalRollups"
|
|
766
|
+
| "funnelRollups"
|
|
767
|
+
| "affiliateRollups"
|
|
768
|
+
| "attributions"
|
|
769
|
+
| "liveSessions"
|
|
770
|
+
| "ingestWindows"
|
|
771
|
+
| "siteIngestWindows";
|
|
772
|
+
siteId: string;
|
|
773
|
+
},
|
|
774
|
+
{ before: number; deleted: number; hasMore: boolean },
|
|
775
|
+
Name
|
|
776
|
+
>;
|
|
777
|
+
disablePolicy: FunctionReference<
|
|
778
|
+
"mutation",
|
|
779
|
+
"internal",
|
|
780
|
+
{ siteId: string },
|
|
781
|
+
null,
|
|
782
|
+
Name
|
|
783
|
+
>;
|
|
784
|
+
runPolicy: FunctionReference<
|
|
785
|
+
"mutation",
|
|
786
|
+
"internal",
|
|
787
|
+
{
|
|
788
|
+
batchSize?: number;
|
|
789
|
+
kind:
|
|
790
|
+
| "events"
|
|
791
|
+
| "sessions"
|
|
792
|
+
| "conversions"
|
|
793
|
+
| "goalCompletions"
|
|
794
|
+
| "aggregateHour"
|
|
795
|
+
| "aggregateDay"
|
|
796
|
+
| "funnelProgress"
|
|
797
|
+
| "funnelSteps"
|
|
798
|
+
| "affiliateStats"
|
|
799
|
+
| "goalRollups"
|
|
800
|
+
| "funnelRollups"
|
|
801
|
+
| "affiliateRollups"
|
|
639
802
|
| "attributions"
|
|
640
803
|
| "liveSessions"
|
|
641
804
|
| "ingestWindows"
|
|
642
805
|
| "siteIngestWindows";
|
|
643
806
|
siteId: string;
|
|
644
807
|
},
|
|
645
|
-
{ deleted: number; hasMore: boolean },
|
|
808
|
+
{ before: number; deleted: number; hasMore: boolean },
|
|
809
|
+
Name
|
|
810
|
+
>;
|
|
811
|
+
setPolicy: FunctionReference<
|
|
812
|
+
"mutation",
|
|
813
|
+
"internal",
|
|
814
|
+
{
|
|
815
|
+
aggregatesDays: number;
|
|
816
|
+
conversionsDays: number;
|
|
817
|
+
eventsDays: number;
|
|
818
|
+
reportsDays: number;
|
|
819
|
+
sessionsDays: number;
|
|
820
|
+
siteId: string;
|
|
821
|
+
},
|
|
822
|
+
null,
|
|
823
|
+
Name
|
|
824
|
+
>;
|
|
825
|
+
status: FunctionReference<
|
|
826
|
+
"query",
|
|
827
|
+
"internal",
|
|
828
|
+
{ siteId: string },
|
|
829
|
+
{
|
|
830
|
+
backlog: number;
|
|
831
|
+
jobs: Array<{
|
|
832
|
+
before: number;
|
|
833
|
+
deleted: number;
|
|
834
|
+
kind:
|
|
835
|
+
| "events"
|
|
836
|
+
| "sessions"
|
|
837
|
+
| "conversions"
|
|
838
|
+
| "goalCompletions"
|
|
839
|
+
| "aggregateHour"
|
|
840
|
+
| "aggregateDay"
|
|
841
|
+
| "funnelProgress"
|
|
842
|
+
| "funnelSteps"
|
|
843
|
+
| "affiliateStats"
|
|
844
|
+
| "goalRollups"
|
|
845
|
+
| "funnelRollups"
|
|
846
|
+
| "affiliateRollups"
|
|
847
|
+
| "attributions"
|
|
848
|
+
| "liveSessions"
|
|
849
|
+
| "ingestWindows"
|
|
850
|
+
| "siteIngestWindows";
|
|
851
|
+
scheduledAt: number | null;
|
|
852
|
+
status: "pending" | "running" | "complete";
|
|
853
|
+
updatedAt: number;
|
|
854
|
+
}>;
|
|
855
|
+
policy: {
|
|
856
|
+
aggregatesDays: number;
|
|
857
|
+
conversionsDays: number;
|
|
858
|
+
eventsDays: number;
|
|
859
|
+
reportsDays: number;
|
|
860
|
+
sessionsDays: number;
|
|
861
|
+
updatedAt: number;
|
|
862
|
+
} | null;
|
|
863
|
+
},
|
|
646
864
|
Name
|
|
647
865
|
>;
|
|
648
866
|
};
|
|
@@ -716,4 +934,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
716
934
|
Name
|
|
717
935
|
>;
|
|
718
936
|
};
|
|
937
|
+
visitors: {
|
|
938
|
+
link: FunctionReference<
|
|
939
|
+
"mutation",
|
|
940
|
+
"internal",
|
|
941
|
+
{ previousVisitorId: string; siteId: string; visitorId: string },
|
|
942
|
+
{ aliasCount: number; linked: boolean },
|
|
943
|
+
Name
|
|
944
|
+
>;
|
|
945
|
+
};
|
|
719
946
|
};
|
|
@@ -0,0 +1,116 @@
|
|
|
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 = count === 16
|
|
64
|
+
? 0.673
|
|
65
|
+
: count === 32
|
|
66
|
+
? 0.697
|
|
67
|
+
: count === 64
|
|
68
|
+
? 0.709
|
|
69
|
+
: 0.7213 / (1 + 1.079 / count);
|
|
70
|
+
let denominator = 0;
|
|
71
|
+
let empty = 0;
|
|
72
|
+
for (const register of registers) {
|
|
73
|
+
denominator += 2 ** -register;
|
|
74
|
+
if (register === 0) empty += 1;
|
|
75
|
+
}
|
|
76
|
+
let estimate = (alpha * count * count) / denominator;
|
|
77
|
+
if (estimate <= 2.5 * count && empty > 0) {
|
|
78
|
+
estimate = count * Math.log(count / empty);
|
|
79
|
+
}
|
|
80
|
+
return Math.max(0, Math.round(estimate));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function checkedRegisters(sketch: VisitorSketch): Uint8Array<ArrayBuffer> {
|
|
84
|
+
if (sketch.version !== SKETCH_VERSION) {
|
|
85
|
+
throw new Error("unsupported visitor sketch version");
|
|
86
|
+
}
|
|
87
|
+
const registers = new Uint8Array(sketch.registers);
|
|
88
|
+
if (registers.length !== 1 << sketch.precision) {
|
|
89
|
+
throw new Error("visitor sketch has invalid register storage");
|
|
90
|
+
}
|
|
91
|
+
return registers;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function leadingZeroRank(value: bigint, bits: number): number {
|
|
95
|
+
if (value === 0n) return bits + 1;
|
|
96
|
+
let rank = 1;
|
|
97
|
+
for (let bit = bits - 1; bit >= 0; bit -= 1) {
|
|
98
|
+
if ((value & (1n << BigInt(bit))) !== 0n) break;
|
|
99
|
+
rank += 1;
|
|
100
|
+
}
|
|
101
|
+
return rank;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function fnv1a64(value: string): bigint {
|
|
105
|
+
let hash = 0xcbf29ce484222325n;
|
|
106
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
107
|
+
hash ^= BigInt(byte);
|
|
108
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
109
|
+
}
|
|
110
|
+
hash ^= hash >> 33n;
|
|
111
|
+
hash = BigInt.asUintN(64, hash * 0xff51afd7ed558ccdn);
|
|
112
|
+
hash ^= hash >> 33n;
|
|
113
|
+
hash = BigInt.asUintN(64, hash * 0xc4ceb9fe1a85ec53n);
|
|
114
|
+
hash ^= hash >> 33n;
|
|
115
|
+
return hash;
|
|
116
|
+
}
|
|
@@ -28,6 +28,17 @@ export const JOURNEY_READ_BYTE_RESERVE = 4 * 1024 * 1024;
|
|
|
28
28
|
export const JOURNEY_READ_DOCUMENT_RESERVE = 1_000;
|
|
29
29
|
export const MAX_RETENTION_BATCH = 500;
|
|
30
30
|
export const MAX_OVERVIEW_AGGREGATE_ROWS = 13_000;
|
|
31
|
+
export const MAX_REPORT_BUCKETS = 5_000;
|
|
32
|
+
export const MAX_FEATURE_ROLLUP_DAYS = 90;
|
|
33
|
+
export const MAX_FEATURE_ROLLUP_ROWS = 5_000;
|
|
34
|
+
export const FEATURE_REPORT_BYTE_RESERVE = 2 * 1024 * 1024;
|
|
35
|
+
export const FEATURE_REPORT_DOCUMENT_RESERVE = 500;
|
|
36
|
+
export const REPORT_ROLLUP_SHARDS = 2;
|
|
37
|
+
export const ROLLUP_SAFETY_DELAY_MS = 5 * 60_000;
|
|
38
|
+
export const RETENTION_LEASE_MS = 60_000;
|
|
31
39
|
|
|
32
40
|
export const DAY_MS = 86_400_000;
|
|
33
41
|
export const HOUR_MS = 3_600_000;
|
|
42
|
+
export const MINUTE_MS = 60_000;
|
|
43
|
+
export const RETENTION_POLICY_INTERVAL_MS = DAY_MS;
|
|
44
|
+
export const MAX_VISITOR_ALIASES = 10;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Id } from "./_generated/dataModel.js";
|
|
2
|
+
import type { MutationCtx } from "./_generated/server.js";
|
|
3
|
+
import { ROLLUP_SAFETY_DELAY_MS } from "./constants.js";
|
|
4
|
+
|
|
5
|
+
export async function ensureAnalyticsControl(
|
|
6
|
+
ctx: MutationCtx,
|
|
7
|
+
siteId: Id<"sites">,
|
|
8
|
+
now: number,
|
|
9
|
+
retained: Array<{ dataset: string; before: number }> = [],
|
|
10
|
+
) {
|
|
11
|
+
const existing = await ctx.db
|
|
12
|
+
.query("analyticsCoverage")
|
|
13
|
+
.withIndex("by_siteId", (range) => range.eq("siteId", siteId))
|
|
14
|
+
.unique();
|
|
15
|
+
if (existing) return existing;
|
|
16
|
+
const fields = {
|
|
17
|
+
siteId,
|
|
18
|
+
generation: 1 as const,
|
|
19
|
+
rollupCompleteFrom: now + ROLLUP_SAFETY_DELAY_MS,
|
|
20
|
+
retained,
|
|
21
|
+
updatedAt: now,
|
|
22
|
+
};
|
|
23
|
+
const _id = await ctx.db.insert("analyticsCoverage", fields);
|
|
24
|
+
return { ...fields, _id, _creationTime: now };
|
|
25
|
+
}
|