@iann29/rastro 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +203 -56
- package/agent/integration.md +17 -15
- package/agent/manifest.json +6 -4
- package/agent/manifest.schema.json +8 -5
- package/dist/client/federation.d.ts +34 -8
- package/dist/client/federation.d.ts.map +1 -1
- package/dist/client/federation.js +17 -3
- package/dist/client/federation.js.map +1 -1
- package/dist/client/index.d.ts +486 -31
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +209 -41
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +8 -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 +82 -8
- package/dist/component/_generated/component.d.ts.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.map +1 -1
- package/dist/component/cardinality.js.map +1 -1
- package/dist/component/constants.d.ts +6 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +12 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/diagnostics.d.ts.map +1 -1
- package/dist/component/diagnostics.js.map +1 -1
- package/dist/component/eventStore.d.ts +3 -1
- package/dist/component/eventStore.d.ts.map +1 -1
- package/dist/component/eventStore.js +13 -10
- package/dist/component/eventStore.js.map +1 -1
- 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.map +1 -1
- package/dist/component/geo.js +3 -2
- package/dist/component/geo.js.map +1 -1
- package/dist/component/goals.d.ts.map +1 -1
- package/dist/component/goals.js +8 -5
- 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 +71 -37
- 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 +168 -29
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/live.d.ts.map +1 -1
- package/dist/component/live.js +1 -3
- package/dist/component/live.js.map +1 -1
- package/dist/component/reports.d.ts +70 -4
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +232 -59
- package/dist/component/reports.js.map +1 -1
- package/dist/component/retention.d.ts +4 -4
- package/dist/component/retention.d.ts.map +1 -1
- package/dist/component/retention.js +54 -22
- package/dist/component/retention.js.map +1 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +42 -11
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +83 -10
- package/dist/component/schema.js +34 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +6 -6
- 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 +29 -18
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +8 -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.map +1 -1
- package/dist/tracker/tracker.js +23 -11
- 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 +137 -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 +71 -71
- package/docs/benchmarks/2026-08-21-formal-certification.md +50 -30
- package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
- package/docs/federation.md +1 -1
- package/package.json +27 -9
- package/scripts/benchmark-ingest.mjs +101 -48
- package/src/component/_generated/api.ts +8 -0
- package/src/component/_generated/component.ts +87 -5
- package/src/component/affiliates.ts +20 -5
- package/src/component/cardinality.ts +8 -7
- package/src/component/constants.ts +12 -0
- package/src/component/diagnostics.ts +3 -2
- package/src/component/eventStore.ts +53 -46
- package/src/component/funnels.ts +19 -16
- package/src/component/geo.ts +16 -14
- package/src/component/goals.ts +29 -21
- package/src/component/guards.ts +3 -1
- package/src/component/http.ts +126 -78
- package/src/component/identity.ts +74 -0
- package/src/component/ingest.ts +349 -130
- package/src/component/live.ts +5 -4
- package/src/component/reports.ts +572 -221
- package/src/component/retention.ts +221 -98
- package/src/component/sanitize.ts +77 -27
- package/src/component/schema.ts +46 -5
- package/src/component/sites.ts +22 -11
- package/src/component/useragent.ts +171 -0
- package/src/component/validators.ts +19 -7
- package/src/component/visitors.ts +116 -0
- package/src/component/vitals.ts +146 -0
package/dist/component/ingest.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
2
|
import { mutation } from "./_generated/server.js";
|
|
3
3
|
import { internal } from "./_generated/api.js";
|
|
4
|
-
import { AGGREGATE_SHARDS, MAX_BATCH_EVENT_GROUPS, DAY_MS, HOURLY_AGGREGATE_SHARDS, DIMENSION_SLOTS, HOUR_MS, LIVE_SESSION_TTL_MS, LIVE_SWEEP_INTERVAL_MS, MAX_BATCH_EVENTS, MAX_SITE_BYTES_PER_SHARD_WINDOW, MAX_SITE_EVENTS_PER_SHARD_WINDOW, MAX_BYTES_PER_SESSION_WINDOW, MAX_EVENTS_PER_SESSION_WINDOW, MAX_FUNNELS_PER_SITE, MAX_FUNNEL_STEPS, MAX_GOALS_PER_SITE, RATE_LIMIT_WINDOW_MS, REPORT_ROLLUP_SHARDS, SECONDARY_AGGREGATE_SHARDS, SITE_INGEST_SHARDS, } from "./constants.js";
|
|
4
|
+
import { AGGREGATE_SHARDS, MAX_BATCH_EVENT_GROUPS, DAY_MS, HOURLY_AGGREGATE_SHARDS, DIMENSION_SLOTS, HOUR_MS, LIVE_SESSION_TTL_MS, LIVE_SWEEP_INTERVAL_MS, MAX_BATCH_EVENTS, MAX_SITE_BYTES_PER_SHARD_WINDOW, MAX_SITE_EVENTS_PER_SHARD_WINDOW, MAX_BYTES_PER_SESSION_WINDOW, MAX_EVENTS_PER_SESSION_WINDOW, MAX_FUNNELS_PER_SITE, MAX_FUNNEL_STEPS, MAX_GOALS_PER_SITE, RATE_LIMIT_WINDOW_MS, REPORT_ROLLUP_SHARDS, SECONDARY_AGGREGATE_SHARDS, SITE_INGEST_SHARDS, VITAL_PAGE_SLOTS, } from "./constants.js";
|
|
5
5
|
import { addVisitor, createVisitorSketch, unionVisitorSketches, } from "./cardinality.js";
|
|
6
|
+
import { addToVitalHistogram, emptyVitalHistogram, mergeVitalHistograms, vitalDevice, VITAL_ALL, VITAL_OTHER_PAGES, } from "./vitals.js";
|
|
6
7
|
import { fail } from "./errors.js";
|
|
7
8
|
import { ensureAnalyticsControl } from "./coverage.js";
|
|
8
9
|
import { bufferEventBatch, eventBatchKey, eventBucketStart, findTelemetry, flushEventBatches, insertEventBatch, inspectTelemetryMinute, } from "./eventStore.js";
|
|
@@ -76,7 +77,7 @@ export const ingestBatch = mutation({
|
|
|
76
77
|
const bucketStart = eventBucketStart(event.timestamp);
|
|
77
78
|
const groupKey = eventBatchKey(event.sessionId, bucketStart);
|
|
78
79
|
requestGroups.add(groupKey);
|
|
79
|
-
if (event.type === "heartbeat")
|
|
80
|
+
if (event.type === "heartbeat" || event.type === "vital")
|
|
80
81
|
continue;
|
|
81
82
|
const eventIds = eventIdsByGroup.get(groupKey) ?? new Set();
|
|
82
83
|
if (eventIds.has(event.eventId)) {
|
|
@@ -144,6 +145,21 @@ export const ingestBatch = mutation({
|
|
|
144
145
|
accepted += 1;
|
|
145
146
|
continue;
|
|
146
147
|
}
|
|
148
|
+
if (event.type === "vital") {
|
|
149
|
+
// A vital annotates telemetry its session already produced; it never
|
|
150
|
+
// creates sessions, live presence, event batches, or aggregate counts.
|
|
151
|
+
if (!existingSession) {
|
|
152
|
+
rejected += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!(await consumeRateLimit(ctx, args.siteId, event.sessionId, eventByteCounts[eventIndex], now, sessionRateWindows))) {
|
|
156
|
+
rejected += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
foldVitalRollups(reportRollupDeltas, args.siteId, event, vitalDevice(context.device));
|
|
160
|
+
accepted += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
147
163
|
if (duplicateInBatch.has(eventIndex) ||
|
|
148
164
|
persistedEventIds
|
|
149
165
|
.get(eventBatchKey(event.sessionId, eventBucketStart(event.timestamp)))
|
|
@@ -173,8 +189,8 @@ export const ingestBatch = mutation({
|
|
|
173
189
|
event,
|
|
174
190
|
context,
|
|
175
191
|
existingSession,
|
|
176
|
-
goals: canMatchDefinition ? goals ?? [] : [],
|
|
177
|
-
funnels: canMatchDefinition ? funnels ?? [] : [],
|
|
192
|
+
goals: canMatchDefinition ? (goals ?? []) : [],
|
|
193
|
+
funnels: canMatchDefinition ? (funnels ?? []) : [],
|
|
178
194
|
aggregateDeltas,
|
|
179
195
|
reportRollupDeltas,
|
|
180
196
|
recordTelemetry: true,
|
|
@@ -376,8 +392,7 @@ async function accountEvent(ctx, input) {
|
|
|
376
392
|
.eq("siteId", input.siteId)
|
|
377
393
|
.eq("goalId", goal._id)
|
|
378
394
|
.eq("eventId", completionEventId))
|
|
379
|
-
.take(2))
|
|
380
|
-
.find((completion) => completion.idempotencyVersion === 2);
|
|
395
|
+
.take(2)).find((completion) => completion.idempotencyVersion === 2);
|
|
381
396
|
const legacyPrior = scopedPrior
|
|
382
397
|
? undefined
|
|
383
398
|
: (await ctx.db
|
|
@@ -386,8 +401,7 @@ async function accountEvent(ctx, input) {
|
|
|
386
401
|
.eq("siteId", input.siteId)
|
|
387
402
|
.eq("goalId", goal._id)
|
|
388
403
|
.eq("eventId", event.eventId))
|
|
389
|
-
.take(2))
|
|
390
|
-
.find((completion) => completion.idempotencyVersion === undefined &&
|
|
404
|
+
.take(2)).find((completion) => completion.idempotencyVersion === undefined &&
|
|
391
405
|
completion.sessionId === event.sessionId &&
|
|
392
406
|
eventBucketStart(completion.timestamp) ===
|
|
393
407
|
eventBucketStart(event.timestamp));
|
|
@@ -415,15 +429,19 @@ async function accountEvent(ctx, input) {
|
|
|
415
429
|
if (input.financialConversion) {
|
|
416
430
|
await accountConversion(ctx, input.siteId, session, event, input.trustedGoalKey, input.reportRollupDeltas);
|
|
417
431
|
}
|
|
432
|
+
const campaignDimensions = session.utmCampaign
|
|
433
|
+
? [{ type: "campaign", value: session.utmCampaign }]
|
|
434
|
+
: [];
|
|
418
435
|
const dimensions = [
|
|
419
436
|
{ type: "source", value: session.source },
|
|
437
|
+
...campaignDimensions,
|
|
420
438
|
{ type: "page", value: event.path },
|
|
421
439
|
{ type: "country", value: session.country ?? "unknown" },
|
|
422
440
|
{ type: "device", value: input.context.device ?? "unknown" },
|
|
423
441
|
{ type: "browser", value: input.context.browser ?? "Other" },
|
|
424
442
|
{
|
|
425
443
|
type: "event",
|
|
426
|
-
value: event.type === "custom" ? event.name ?? "custom" : event.type,
|
|
444
|
+
value: event.type === "custom" ? (event.name ?? "custom") : event.type,
|
|
427
445
|
},
|
|
428
446
|
...matchedGoalKeys.map((value) => ({ type: "goal", value })),
|
|
429
447
|
];
|
|
@@ -450,6 +468,8 @@ async function accountTrustedSession(ctx, existing, event) {
|
|
|
450
468
|
isNewVisitorDay: false,
|
|
451
469
|
startedAt: existing.startedAt,
|
|
452
470
|
source: existing.source,
|
|
471
|
+
utmCampaign: existing.utmCampaign,
|
|
472
|
+
visitorKey: existing.visitorKey ?? existing.visitorId,
|
|
453
473
|
country: existing.country,
|
|
454
474
|
city: existing.city,
|
|
455
475
|
latitude: existing.latitude,
|
|
@@ -463,14 +483,21 @@ async function accountTrustedSession(ctx, existing, event) {
|
|
|
463
483
|
};
|
|
464
484
|
}
|
|
465
485
|
async function upsertSession(ctx, siteId, event, context, existing, financialConversion, reportRollupDeltas, batchState) {
|
|
466
|
-
|
|
486
|
+
// A host-supplied id is the identity; anonymous sessions count through the
|
|
487
|
+
// daily key derived by the HTTP action, or once per session without one.
|
|
488
|
+
const visitorKey = existing?.visitorKey ??
|
|
489
|
+
(event.visitorId !== event.sessionId
|
|
490
|
+
? event.visitorId
|
|
491
|
+
: (context.visitorKey ?? event.sessionId));
|
|
467
492
|
const utmSource = stringProperty(event.properties, "utm_source");
|
|
493
|
+
// Campaign traffic names its own source; the referrer host is the fallback.
|
|
494
|
+
const source = utmSource?.toLowerCase() ?? sourceFromReferrer(event.referrer);
|
|
468
495
|
const utmMedium = stringProperty(event.properties, "utm_medium");
|
|
469
496
|
const utmCampaign = stringProperty(event.properties, "utm_campaign");
|
|
470
497
|
const attribution = await attributionForEvent(ctx, siteId, event, existing, batchState);
|
|
471
498
|
if (existing) {
|
|
472
499
|
if (attribution && existing.affiliateId !== attribution.affiliateId) {
|
|
473
|
-
foldAffiliateVisitRollup(reportRollupDeltas, siteId, event, attribution);
|
|
500
|
+
foldAffiliateVisitRollup(reportRollupDeltas, siteId, event, attribution, visitorKey);
|
|
474
501
|
}
|
|
475
502
|
const startedAt = Math.min(existing.startedAt, event.timestamp);
|
|
476
503
|
const lastSeenAt = Math.max(existing.lastSeenAt, event.timestamp);
|
|
@@ -484,7 +511,7 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
484
511
|
durationMs: Math.max(0, lastSeenAt - startedAt),
|
|
485
512
|
conversionCount: existing.conversionCount + Number(financialConversion),
|
|
486
513
|
revenueCents: existing.revenueCents +
|
|
487
|
-
(financialConversion ? event.revenueCents ?? 0 : 0),
|
|
514
|
+
(financialConversion ? (event.revenueCents ?? 0) : 0),
|
|
488
515
|
...(financialConversion && event.currency
|
|
489
516
|
? { currency: event.currency }
|
|
490
517
|
: {}),
|
|
@@ -495,7 +522,9 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
495
522
|
...(context.country ? { country: context.country } : {}),
|
|
496
523
|
...(context.city ? { city: context.city } : {}),
|
|
497
524
|
...(context.latitude !== undefined ? { latitude: context.latitude } : {}),
|
|
498
|
-
...(context.longitude !== undefined
|
|
525
|
+
...(context.longitude !== undefined
|
|
526
|
+
? { longitude: context.longitude }
|
|
527
|
+
: {}),
|
|
499
528
|
browser: context.browser ?? existing.browser,
|
|
500
529
|
os: context.os ?? existing.os,
|
|
501
530
|
device: context.device ?? existing.device,
|
|
@@ -518,6 +547,8 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
518
547
|
Math.floor(event.timestamp / DAY_MS),
|
|
519
548
|
startedAt,
|
|
520
549
|
source: existing.source,
|
|
550
|
+
utmCampaign: existing.utmCampaign,
|
|
551
|
+
visitorKey,
|
|
521
552
|
country: context.country ?? existing.country,
|
|
522
553
|
city: context.city ?? existing.city,
|
|
523
554
|
latitude: context.latitude ?? existing.latitude,
|
|
@@ -547,6 +578,7 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
547
578
|
siteId,
|
|
548
579
|
sessionId: event.sessionId,
|
|
549
580
|
visitorId: event.visitorId,
|
|
581
|
+
visitorKey,
|
|
550
582
|
startedAt: event.timestamp,
|
|
551
583
|
lastSeenAt: event.timestamp,
|
|
552
584
|
entryPath: event.path,
|
|
@@ -559,7 +591,9 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
559
591
|
...(context.country ? { country: context.country } : {}),
|
|
560
592
|
...(context.city ? { city: context.city } : {}),
|
|
561
593
|
...(context.latitude !== undefined ? { latitude: context.latitude } : {}),
|
|
562
|
-
...(context.longitude !== undefined
|
|
594
|
+
...(context.longitude !== undefined
|
|
595
|
+
? { longitude: context.longitude }
|
|
596
|
+
: {}),
|
|
563
597
|
browser: context.browser ?? "Other",
|
|
564
598
|
os: context.os ?? "Other",
|
|
565
599
|
device: context.device ?? "unknown",
|
|
@@ -567,7 +601,7 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
567
601
|
pageviewCount: event.type === "pageview" ? 1 : 0,
|
|
568
602
|
durationMs: 0,
|
|
569
603
|
conversionCount: Number(financialConversion),
|
|
570
|
-
revenueCents: financialConversion ? event.revenueCents ?? 0 : 0,
|
|
604
|
+
revenueCents: financialConversion ? (event.revenueCents ?? 0) : 0,
|
|
571
605
|
...(financialConversion && event.currency
|
|
572
606
|
? { currency: event.currency }
|
|
573
607
|
: {}),
|
|
@@ -575,7 +609,7 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
575
609
|
};
|
|
576
610
|
const sessionId = await ctx.db.insert("sessions", fields);
|
|
577
611
|
if (attribution) {
|
|
578
|
-
foldAffiliateVisitRollup(reportRollupDeltas, siteId, event, attribution);
|
|
612
|
+
foldAffiliateVisitRollup(reportRollupDeltas, siteId, event, attribution, visitorKey);
|
|
579
613
|
}
|
|
580
614
|
if (batchState) {
|
|
581
615
|
const state = batchState.sessions.get(event.sessionId);
|
|
@@ -596,6 +630,8 @@ async function upsertSession(ctx, siteId, event, context, existing, financialCon
|
|
|
596
630
|
Math.floor(event.timestamp / DAY_MS),
|
|
597
631
|
startedAt: event.timestamp,
|
|
598
632
|
source,
|
|
633
|
+
utmCampaign,
|
|
634
|
+
visitorKey,
|
|
599
635
|
country: context.country,
|
|
600
636
|
city: context.city,
|
|
601
637
|
latitude: context.latitude,
|
|
@@ -719,14 +755,14 @@ async function accountHeartbeat(ctx, siteId, session, event, context, now, batch
|
|
|
719
755
|
const updates = {
|
|
720
756
|
startedAt,
|
|
721
757
|
lastSeenAt,
|
|
722
|
-
exitPath: event.timestamp >= session.lastSeenAt
|
|
723
|
-
? event.path
|
|
724
|
-
: session.exitPath,
|
|
758
|
+
exitPath: event.timestamp >= session.lastSeenAt ? event.path : session.exitPath,
|
|
725
759
|
durationMs: Math.max(0, lastSeenAt - startedAt),
|
|
726
760
|
...(context.country ? { country: context.country } : {}),
|
|
727
761
|
...(context.city ? { city: context.city } : {}),
|
|
728
762
|
...(context.latitude !== undefined ? { latitude: context.latitude } : {}),
|
|
729
|
-
...(context.longitude !== undefined
|
|
763
|
+
...(context.longitude !== undefined
|
|
764
|
+
? { longitude: context.longitude }
|
|
765
|
+
: {}),
|
|
730
766
|
browser: context.browser ?? session.browser,
|
|
731
767
|
os: context.os ?? session.os,
|
|
732
768
|
device: context.device ?? session.device,
|
|
@@ -747,6 +783,7 @@ async function accountHeartbeat(ctx, siteId, session, event, context, now, batch
|
|
|
747
783
|
isNewVisitorDay: false,
|
|
748
784
|
startedAt,
|
|
749
785
|
source: session.source,
|
|
786
|
+
visitorKey: session.visitorKey ?? session.visitorId,
|
|
750
787
|
country: context.country ?? session.country,
|
|
751
788
|
city: context.city ?? session.city,
|
|
752
789
|
latitude: context.latitude ?? session.latitude,
|
|
@@ -776,9 +813,7 @@ async function consumeSiteRateLimit(ctx, siteId, events, eventByteCounts, now) {
|
|
|
776
813
|
.order("desc")
|
|
777
814
|
.take(1);
|
|
778
815
|
const storedWindow = rows[0] ?? null;
|
|
779
|
-
const window = storedWindow?.windowStart === windowStart
|
|
780
|
-
? storedWindow
|
|
781
|
-
: null;
|
|
816
|
+
const window = storedWindow?.windowStart === windowStart ? storedWindow : null;
|
|
782
817
|
if ((window?.eventCount ?? 0) + increment.eventCount >
|
|
783
818
|
MAX_SITE_EVENTS_PER_SHARD_WINDOW ||
|
|
784
819
|
(window?.byteCount ?? 0) + increment.byteCount >
|
|
@@ -1039,7 +1074,8 @@ async function advanceFunnel(ctx, siteId, funnel, event, reportRollupDeltas, bat
|
|
|
1039
1074
|
foldFunnelRollup(reportRollupDeltas, siteId, funnel._id, event, 0, false);
|
|
1040
1075
|
return;
|
|
1041
1076
|
}
|
|
1042
|
-
if (progress.completedAt !== undefined ||
|
|
1077
|
+
if (progress.completedAt !== undefined ||
|
|
1078
|
+
event.timestamp < progress.lastStepAt)
|
|
1043
1079
|
return;
|
|
1044
1080
|
if (event.timestamp - progress.startedAt > funnel.conversionWindowMs) {
|
|
1045
1081
|
if (!matchesFirst)
|
|
@@ -1191,6 +1227,7 @@ function createReportRollupDeltas() {
|
|
|
1191
1227
|
goals: new Map(),
|
|
1192
1228
|
funnels: new Map(),
|
|
1193
1229
|
affiliates: new Map(),
|
|
1230
|
+
vitals: new Map(),
|
|
1194
1231
|
};
|
|
1195
1232
|
}
|
|
1196
1233
|
function reportRollupCoordinates(siteId, definitionId, timestamp, visitorId) {
|
|
@@ -1236,7 +1273,7 @@ function foldFunnelRollup(deltas, siteId, funnelId, event, step, completed) {
|
|
|
1236
1273
|
rollup.steps.set(step, current);
|
|
1237
1274
|
deltas.funnels.set(coordinates.key, rollup);
|
|
1238
1275
|
}
|
|
1239
|
-
function foldAffiliateVisitRollup(deltas, siteId, event, attribution) {
|
|
1276
|
+
function foldAffiliateVisitRollup(deltas, siteId, event, attribution, visitorKey) {
|
|
1240
1277
|
const coordinates = reportRollupCoordinates(siteId, String(attribution.affiliateId), event.timestamp, event.visitorId);
|
|
1241
1278
|
const current = deltas.affiliates.get(coordinates.key) ?? {
|
|
1242
1279
|
siteId,
|
|
@@ -1251,7 +1288,7 @@ function foldAffiliateVisitRollup(deltas, siteId, event, attribution) {
|
|
|
1251
1288
|
commissionCents: 0,
|
|
1252
1289
|
};
|
|
1253
1290
|
current.visits += 1;
|
|
1254
|
-
current.visitorSketch = addVisitor(current.visitorSketch,
|
|
1291
|
+
current.visitorSketch = addVisitor(current.visitorSketch, visitorKey);
|
|
1255
1292
|
deltas.affiliates.set(coordinates.key, current);
|
|
1256
1293
|
}
|
|
1257
1294
|
function foldAffiliateConversionRollup(deltas, siteId, event, affiliate, affiliateSlug, revenueCents, commissionCents) {
|
|
@@ -1273,6 +1310,103 @@ function foldAffiliateConversionRollup(deltas, siteId, event, affiliate, affilia
|
|
|
1273
1310
|
current.commissionCents += commissionCents;
|
|
1274
1311
|
deltas.affiliates.set(coordinates.key, current);
|
|
1275
1312
|
}
|
|
1313
|
+
function foldVitalRollups(deltas, siteId, event, device) {
|
|
1314
|
+
const metric = event.name;
|
|
1315
|
+
const value = event.value ?? 0;
|
|
1316
|
+
const bucketStart = Math.floor(event.timestamp / DAY_MS) * DAY_MS;
|
|
1317
|
+
const shard = stableHash(event.sessionId) % REPORT_ROLLUP_SHARDS;
|
|
1318
|
+
// Each measurement lands twice: on its page (across devices) and on its
|
|
1319
|
+
// device class (across pages), so both breakdowns stay exact and bounded.
|
|
1320
|
+
for (const target of [
|
|
1321
|
+
{ device: VITAL_ALL, page: event.path },
|
|
1322
|
+
{ device, page: VITAL_ALL },
|
|
1323
|
+
]) {
|
|
1324
|
+
const key = JSON.stringify([
|
|
1325
|
+
siteId,
|
|
1326
|
+
bucketStart,
|
|
1327
|
+
target.device,
|
|
1328
|
+
target.page,
|
|
1329
|
+
shard,
|
|
1330
|
+
]);
|
|
1331
|
+
const row = deltas.vitals.get(key) ?? {
|
|
1332
|
+
siteId,
|
|
1333
|
+
bucketStart,
|
|
1334
|
+
device: target.device,
|
|
1335
|
+
page: target.page,
|
|
1336
|
+
shard,
|
|
1337
|
+
metrics: new Map(),
|
|
1338
|
+
};
|
|
1339
|
+
const entry = row.metrics.get(metric) ?? {
|
|
1340
|
+
count: 0,
|
|
1341
|
+
sum: 0,
|
|
1342
|
+
histogram: emptyVitalHistogram(),
|
|
1343
|
+
};
|
|
1344
|
+
entry.count += 1;
|
|
1345
|
+
entry.sum += value;
|
|
1346
|
+
entry.histogram = addToVitalHistogram(entry.histogram, value);
|
|
1347
|
+
row.metrics.set(metric, entry);
|
|
1348
|
+
deltas.vitals.set(key, row);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function upsertVitalRollup(ctx, delta) {
|
|
1352
|
+
const existing = await ctx.db
|
|
1353
|
+
.query("vitalRollups")
|
|
1354
|
+
.withIndex("by_key", (range) => range
|
|
1355
|
+
.eq("siteId", delta.siteId)
|
|
1356
|
+
.eq("bucketStart", delta.bucketStart)
|
|
1357
|
+
.eq("device", delta.device)
|
|
1358
|
+
.eq("page", delta.page)
|
|
1359
|
+
.eq("shard", delta.shard))
|
|
1360
|
+
.unique();
|
|
1361
|
+
if (!existing &&
|
|
1362
|
+
delta.device === VITAL_ALL &&
|
|
1363
|
+
delta.page !== VITAL_OTHER_PAGES) {
|
|
1364
|
+
// A new page row spends one bounded read to respect the daily page slots;
|
|
1365
|
+
// past the cap, the day's remaining pages fold into "(other)".
|
|
1366
|
+
const pageRows = await ctx.db
|
|
1367
|
+
.query("vitalRollups")
|
|
1368
|
+
.withIndex("by_key", (range) => range
|
|
1369
|
+
.eq("siteId", delta.siteId)
|
|
1370
|
+
.eq("bucketStart", delta.bucketStart)
|
|
1371
|
+
.eq("device", VITAL_ALL))
|
|
1372
|
+
.take(VITAL_PAGE_SLOTS * REPORT_ROLLUP_SHARDS + 1);
|
|
1373
|
+
if (pageRows.length > VITAL_PAGE_SLOTS * REPORT_ROLLUP_SHARDS) {
|
|
1374
|
+
await upsertVitalRollup(ctx, { ...delta, page: VITAL_OTHER_PAGES });
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
const metrics = existing?.metrics.map((entry) => ({ ...entry })) ?? [];
|
|
1379
|
+
for (const [metric, addition] of delta.metrics) {
|
|
1380
|
+
const entry = metrics.find((candidate) => candidate.metric === metric);
|
|
1381
|
+
if (entry) {
|
|
1382
|
+
entry.count += addition.count;
|
|
1383
|
+
entry.sum += addition.sum;
|
|
1384
|
+
entry.histogram = mergeVitalHistograms(entry.histogram, addition.histogram);
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
metrics.push({
|
|
1388
|
+
metric,
|
|
1389
|
+
count: addition.count,
|
|
1390
|
+
sum: addition.sum,
|
|
1391
|
+
histogram: [...addition.histogram],
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
const fields = {
|
|
1396
|
+
siteId: delta.siteId,
|
|
1397
|
+
bucketStart: delta.bucketStart,
|
|
1398
|
+
device: delta.device,
|
|
1399
|
+
page: delta.page,
|
|
1400
|
+
shard: delta.shard,
|
|
1401
|
+
metrics,
|
|
1402
|
+
};
|
|
1403
|
+
if (existing) {
|
|
1404
|
+
await ctx.db.replace("vitalRollups", existing._id, fields);
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
await ctx.db.insert("vitalRollups", fields);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1276
1410
|
async function flushReportRollups(ctx, deltas) {
|
|
1277
1411
|
for (const delta of deltas.goals.values()) {
|
|
1278
1412
|
const existing = await ctx.db
|
|
@@ -1367,9 +1501,12 @@ async function flushReportRollups(ctx, deltas) {
|
|
|
1367
1501
|
await ctx.db.insert("affiliateDailyRollups", fields);
|
|
1368
1502
|
}
|
|
1369
1503
|
}
|
|
1504
|
+
for (const delta of deltas.vitals.values()) {
|
|
1505
|
+
await upsertVitalRollup(ctx, delta);
|
|
1506
|
+
}
|
|
1370
1507
|
}
|
|
1371
1508
|
function foldAggregates(aggregateDeltas, siteId, event, session, dimensions, recordTelemetry, financialConversion) {
|
|
1372
|
-
const revenueCents = financialConversion ? event.revenueCents ?? 0 : 0;
|
|
1509
|
+
const revenueCents = financialConversion ? (event.revenueCents ?? 0) : 0;
|
|
1373
1510
|
const dimensionDeltas = dimensions.map((dimension) => ({
|
|
1374
1511
|
...dimension,
|
|
1375
1512
|
count: Number(recordTelemetry),
|
|
@@ -1415,7 +1552,7 @@ function foldAggregates(aggregateDeltas, siteId, event, session, dimensions, rec
|
|
|
1415
1552
|
: Number(session.isNewVisitorDay)
|
|
1416
1553
|
: 0;
|
|
1417
1554
|
if (recordTelemetry) {
|
|
1418
|
-
delta.visitorSketch = addVisitor(delta.visitorSketch,
|
|
1555
|
+
delta.visitorSketch = addVisitor(delta.visitorSketch, session.visitorKey);
|
|
1419
1556
|
}
|
|
1420
1557
|
delta.conversions += Number(financialConversion);
|
|
1421
1558
|
delta.revenueCents += revenueCents;
|
|
@@ -1539,6 +1676,8 @@ function mergeDimensions(current, additions) {
|
|
|
1539
1676
|
}
|
|
1540
1677
|
function stringProperty(properties, key) {
|
|
1541
1678
|
const value = properties?.[key];
|
|
1542
|
-
return typeof value === "string"
|
|
1679
|
+
return typeof value === "string"
|
|
1680
|
+
? cleanString(value, 120) || undefined
|
|
1681
|
+
: undefined;
|
|
1543
1682
|
}
|
|
1544
1683
|
//# sourceMappingURL=ingest.js.map
|