@iann29/rastro 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +325 -63
- 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 +1600 -8
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +210 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +9 -5
- 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 +161 -1
- 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/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 +10 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +10 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/convex.config.d.ts +7 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +9 -4
- package/dist/component/convex.config.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/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 +11 -9
- package/dist/component/eventStore.d.ts.map +1 -1
- package/dist/component/eventStore.js +55 -227
- package/dist/component/eventStore.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 +610 -0
- package/dist/component/geo.js.map +1 -0
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +168 -17
- package/dist/component/http.js.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +272 -32
- 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 +117 -8
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +641 -79
- 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 +9 -4
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +202 -53
- package/dist/component/schema.js +113 -16
- 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/validators.d.ts +29 -51
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +11 -12
- package/dist/component/validators.js.map +1 -1
- 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 +7 -6
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/benchmarks/2026-08-20-realistic.md +5 -5
- 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 +29 -7
- package/scripts/benchmark-ingest.mjs +81 -32
- package/scripts/generate-federation-keys.mjs +20 -0
- package/src/component/_generated/api.ts +9 -5
- package/src/component/_generated/component.ts +224 -1
- package/src/component/_generated/server.ts +4 -0
- package/src/component/cardinality.ts +116 -0
- package/src/component/constants.ts +10 -0
- package/src/component/convex.config.ts +11 -5
- package/src/component/coverage.ts +25 -0
- package/src/component/diagnostics.ts +64 -0
- package/src/component/errors.ts +2 -1
- package/src/component/eventStore.ts +78 -280
- package/src/component/geo.ts +779 -0
- package/src/component/http.ts +259 -22
- package/src/component/ingest.ts +436 -27
- package/src/component/live.ts +8 -3
- package/src/component/reports.ts +895 -87
- package/src/component/retention.ts +624 -88
- package/src/component/sanitize.ts +15 -4
- package/src/component/schema.ts +125 -16
- package/src/component/sites.ts +5 -1
- package/src/component/validators.ts +12 -13
- package/src/test.ts +0 -2
- package/dist/component/migrations.d.ts +0 -17
- package/dist/component/migrations.d.ts.map +0 -1
- package/dist/component/migrations.js +0 -44
- package/dist/component/migrations.js.map +0 -1
- package/src/component/migrations.ts +0 -52
package/src/component/ingest.ts
CHANGED
|
@@ -18,12 +18,21 @@ import {
|
|
|
18
18
|
MAX_BYTES_PER_SESSION_WINDOW,
|
|
19
19
|
MAX_EVENTS_PER_SESSION_WINDOW,
|
|
20
20
|
MAX_FUNNELS_PER_SITE,
|
|
21
|
+
MAX_FUNNEL_STEPS,
|
|
21
22
|
MAX_GOALS_PER_SITE,
|
|
22
23
|
RATE_LIMIT_WINDOW_MS,
|
|
24
|
+
REPORT_ROLLUP_SHARDS,
|
|
23
25
|
SECONDARY_AGGREGATE_SHARDS,
|
|
24
26
|
SITE_INGEST_SHARDS,
|
|
25
27
|
} from "./constants.js";
|
|
28
|
+
import {
|
|
29
|
+
addVisitor,
|
|
30
|
+
createVisitorSketch,
|
|
31
|
+
unionVisitorSketches,
|
|
32
|
+
type VisitorSketch,
|
|
33
|
+
} from "./cardinality.js";
|
|
26
34
|
import { fail } from "./errors.js";
|
|
35
|
+
import { ensureAnalyticsControl } from "./coverage.js";
|
|
27
36
|
import {
|
|
28
37
|
bufferEventBatch,
|
|
29
38
|
eventBatchKey,
|
|
@@ -36,8 +45,8 @@ import {
|
|
|
36
45
|
} from "./eventStore.js";
|
|
37
46
|
import {
|
|
38
47
|
cleanString,
|
|
39
|
-
ensureBatchBytes,
|
|
40
48
|
isValidMoney,
|
|
49
|
+
measureBatchBytes,
|
|
41
50
|
originAllowed,
|
|
42
51
|
sanitizeContext,
|
|
43
52
|
sanitizeCurrency,
|
|
@@ -81,11 +90,49 @@ type AggregateDelta = {
|
|
|
81
90
|
outboundClicks: number;
|
|
82
91
|
sessions: number;
|
|
83
92
|
visitors: number;
|
|
93
|
+
visitorSketch: VisitorSketch;
|
|
84
94
|
conversions: number;
|
|
85
95
|
revenueCents: number;
|
|
86
96
|
dimensions: Doc<"aggregateBuckets">["dimensions"];
|
|
87
97
|
};
|
|
88
98
|
type AggregateDeltas = Map<string, AggregateDelta>;
|
|
99
|
+
type GoalRollupDelta = {
|
|
100
|
+
siteId: Id<"sites">;
|
|
101
|
+
goalId: Id<"goals">;
|
|
102
|
+
bucketStart: number;
|
|
103
|
+
shard: number;
|
|
104
|
+
goalKey: string;
|
|
105
|
+
completions: number;
|
|
106
|
+
valueCents: number;
|
|
107
|
+
};
|
|
108
|
+
type FunnelRollupDelta = {
|
|
109
|
+
siteId: Id<"sites">;
|
|
110
|
+
funnelId: Id<"funnels">;
|
|
111
|
+
bucketStart: number;
|
|
112
|
+
shard: number;
|
|
113
|
+
steps: Map<number, {
|
|
114
|
+
step: number;
|
|
115
|
+
entrants: number;
|
|
116
|
+
completions: number;
|
|
117
|
+
}>;
|
|
118
|
+
};
|
|
119
|
+
type AffiliateRollupDelta = {
|
|
120
|
+
siteId: Id<"sites">;
|
|
121
|
+
affiliateId: Id<"affiliates">;
|
|
122
|
+
bucketStart: number;
|
|
123
|
+
shard: number;
|
|
124
|
+
affiliateSlug: string;
|
|
125
|
+
visits: number;
|
|
126
|
+
visitorSketch: VisitorSketch;
|
|
127
|
+
conversions: number;
|
|
128
|
+
revenueCents: number;
|
|
129
|
+
commissionCents: number;
|
|
130
|
+
};
|
|
131
|
+
type ReportRollupDeltas = {
|
|
132
|
+
goals: Map<string, GoalRollupDelta>;
|
|
133
|
+
funnels: Map<string, FunnelRollupDelta>;
|
|
134
|
+
affiliates: Map<string, AffiliateRollupDelta>;
|
|
135
|
+
};
|
|
89
136
|
type AttributionSnapshot = {
|
|
90
137
|
affiliateId: Id<"affiliates">;
|
|
91
138
|
affiliateSlug: string;
|
|
@@ -99,6 +146,10 @@ type SessionState = {
|
|
|
99
146
|
isNewVisitorDay: boolean;
|
|
100
147
|
startedAt: number;
|
|
101
148
|
source: string;
|
|
149
|
+
country?: string;
|
|
150
|
+
city?: string;
|
|
151
|
+
latitude?: number;
|
|
152
|
+
longitude?: number;
|
|
102
153
|
pageviewCount: number;
|
|
103
154
|
eventCount: number;
|
|
104
155
|
affiliateId?: Id<"affiliates">;
|
|
@@ -157,21 +208,25 @@ export const ingestBatch = mutation({
|
|
|
157
208
|
limit: MAX_BATCH_EVENTS,
|
|
158
209
|
});
|
|
159
210
|
}
|
|
211
|
+
let eventByteCounts: number[];
|
|
160
212
|
try {
|
|
161
|
-
|
|
213
|
+
({ eventByteCounts } = measureBatchBytes(String(args.siteId), args.events));
|
|
162
214
|
} catch {
|
|
163
215
|
fail("BATCH_TOO_LARGE", "batch exceeds 64 KiB", { limitBytes: 65_536 });
|
|
164
216
|
}
|
|
165
217
|
|
|
166
218
|
const now = Date.now();
|
|
167
|
-
|
|
219
|
+
await ensureAnalyticsControl(ctx, args.siteId, now);
|
|
220
|
+
await consumeSiteRateLimit(
|
|
168
221
|
ctx,
|
|
169
222
|
args.siteId,
|
|
170
223
|
args.events,
|
|
224
|
+
eventByteCounts,
|
|
171
225
|
now,
|
|
172
226
|
);
|
|
173
227
|
const context = sanitizeContext(args.context);
|
|
174
228
|
const aggregateDeltas: AggregateDeltas = new Map();
|
|
229
|
+
const reportRollupDeltas = createReportRollupDeltas();
|
|
175
230
|
const sessionRateWindows = new Map<string, SessionRateWindowState>();
|
|
176
231
|
const batchState: BatchState = {
|
|
177
232
|
sessions: new Map(),
|
|
@@ -236,7 +291,6 @@ export const ingestBatch = mutation({
|
|
|
236
291
|
siteId: args.siteId,
|
|
237
292
|
sessionId: group.sessionId,
|
|
238
293
|
bucketStart: group.bucketStart,
|
|
239
|
-
candidateEventIds: group.eventIds,
|
|
240
294
|
});
|
|
241
295
|
const newCount = [...group.eventIds].filter((eventId) =>
|
|
242
296
|
!eventIds.has(eventId)
|
|
@@ -356,7 +410,7 @@ export const ingestBatch = mutation({
|
|
|
356
410
|
.withIndex("by_siteId", (range) => range.eq("siteId", args.siteId))
|
|
357
411
|
.take(MAX_FUNNELS_PER_SITE);
|
|
358
412
|
}
|
|
359
|
-
await accountEvent(ctx, {
|
|
413
|
+
const session = await accountEvent(ctx, {
|
|
360
414
|
siteId: args.siteId,
|
|
361
415
|
event,
|
|
362
416
|
context,
|
|
@@ -364,18 +418,25 @@ export const ingestBatch = mutation({
|
|
|
364
418
|
goals: canMatchDefinition ? goals ?? [] : [],
|
|
365
419
|
funnels: canMatchDefinition ? funnels ?? [] : [],
|
|
366
420
|
aggregateDeltas,
|
|
421
|
+
reportRollupDeltas,
|
|
367
422
|
recordTelemetry: true,
|
|
368
423
|
financialConversion: false,
|
|
369
424
|
trustedGoalKey: undefined,
|
|
370
425
|
now,
|
|
371
426
|
batchState,
|
|
372
427
|
});
|
|
373
|
-
bufferEventBatch(
|
|
428
|
+
bufferEventBatch(
|
|
429
|
+
args.siteId,
|
|
430
|
+
event,
|
|
431
|
+
batchState.eventBatches,
|
|
432
|
+
session.country ?? "unknown",
|
|
433
|
+
);
|
|
374
434
|
accepted += 1;
|
|
375
435
|
}
|
|
376
436
|
await flushBatchState(ctx, batchState);
|
|
377
437
|
await flushSessionRateLimits(ctx, sessionRateWindows, now);
|
|
378
438
|
await flushAggregates(ctx, aggregateDeltas);
|
|
439
|
+
await flushReportRollups(ctx, reportRollupDeltas);
|
|
379
440
|
return { accepted, duplicates, rejected };
|
|
380
441
|
},
|
|
381
442
|
});
|
|
@@ -467,7 +528,8 @@ export const trackConversion = mutation({
|
|
|
467
528
|
};
|
|
468
529
|
|
|
469
530
|
const aggregateDeltas: AggregateDeltas = new Map();
|
|
470
|
-
|
|
531
|
+
const reportRollupDeltas = createReportRollupDeltas();
|
|
532
|
+
const session = await accountEvent(ctx, {
|
|
471
533
|
siteId: args.siteId,
|
|
472
534
|
event,
|
|
473
535
|
context: conversionContext,
|
|
@@ -475,15 +537,23 @@ export const trackConversion = mutation({
|
|
|
475
537
|
goals: [],
|
|
476
538
|
funnels: [],
|
|
477
539
|
aggregateDeltas,
|
|
540
|
+
reportRollupDeltas,
|
|
478
541
|
recordTelemetry: existingTelemetry === null && !telemetry.saturated,
|
|
479
542
|
financialConversion: true,
|
|
480
543
|
trustedGoalKey,
|
|
481
544
|
now: Date.now(),
|
|
482
545
|
});
|
|
483
546
|
if (!existingTelemetry && !telemetry.saturated) {
|
|
484
|
-
await insertEventBatch(
|
|
547
|
+
await insertEventBatch(
|
|
548
|
+
ctx,
|
|
549
|
+
args.siteId,
|
|
550
|
+
event,
|
|
551
|
+
session.country ?? "unknown",
|
|
552
|
+
);
|
|
485
553
|
}
|
|
486
554
|
await flushAggregates(ctx, aggregateDeltas);
|
|
555
|
+
await flushReportRollups(ctx, reportRollupDeltas);
|
|
556
|
+
await ensureAnalyticsControl(ctx, args.siteId, Date.now());
|
|
487
557
|
return { accepted: 1, duplicates: 0, rejected: 0 };
|
|
488
558
|
},
|
|
489
559
|
});
|
|
@@ -606,13 +676,14 @@ async function accountEvent(
|
|
|
606
676
|
goals: Doc<"goals">[];
|
|
607
677
|
funnels: Doc<"funnels">[];
|
|
608
678
|
aggregateDeltas: AggregateDeltas;
|
|
679
|
+
reportRollupDeltas: ReportRollupDeltas;
|
|
609
680
|
recordTelemetry: boolean;
|
|
610
681
|
financialConversion: boolean;
|
|
611
682
|
trustedGoalKey: string | undefined;
|
|
612
683
|
now: number;
|
|
613
684
|
batchState?: BatchState;
|
|
614
685
|
},
|
|
615
|
-
) {
|
|
686
|
+
): Promise<SessionState> {
|
|
616
687
|
const { event } = input;
|
|
617
688
|
const session = input.recordTelemetry
|
|
618
689
|
? await upsertSession(
|
|
@@ -622,6 +693,7 @@ async function accountEvent(
|
|
|
622
693
|
input.context,
|
|
623
694
|
input.existingSession,
|
|
624
695
|
input.financialConversion,
|
|
696
|
+
input.reportRollupDeltas,
|
|
625
697
|
input.batchState,
|
|
626
698
|
)
|
|
627
699
|
: await accountTrustedSession(ctx, input.existingSession, event);
|
|
@@ -678,12 +750,25 @@ async function accountEvent(
|
|
|
678
750
|
timestamp: event.timestamp,
|
|
679
751
|
valueCents: goal.valueCents ?? 0,
|
|
680
752
|
});
|
|
753
|
+
foldGoalRollup(
|
|
754
|
+
input.reportRollupDeltas,
|
|
755
|
+
input.siteId,
|
|
756
|
+
event,
|
|
757
|
+
goal,
|
|
758
|
+
);
|
|
681
759
|
matchedGoalKeys.push(goal.key);
|
|
682
760
|
}
|
|
683
761
|
|
|
684
762
|
for (const funnel of input.funnels) {
|
|
685
763
|
if (funnel.active) {
|
|
686
|
-
await advanceFunnel(
|
|
764
|
+
await advanceFunnel(
|
|
765
|
+
ctx,
|
|
766
|
+
input.siteId,
|
|
767
|
+
funnel,
|
|
768
|
+
event,
|
|
769
|
+
input.reportRollupDeltas,
|
|
770
|
+
input.batchState,
|
|
771
|
+
);
|
|
687
772
|
}
|
|
688
773
|
}
|
|
689
774
|
|
|
@@ -694,13 +779,14 @@ async function accountEvent(
|
|
|
694
779
|
session,
|
|
695
780
|
event,
|
|
696
781
|
input.trustedGoalKey,
|
|
782
|
+
input.reportRollupDeltas,
|
|
697
783
|
);
|
|
698
784
|
}
|
|
699
785
|
|
|
700
786
|
const dimensions: Dimension[] = [
|
|
701
787
|
{ type: "source", value: session.source },
|
|
702
788
|
{ type: "page", value: event.path },
|
|
703
|
-
{ type: "country", value:
|
|
789
|
+
{ type: "country", value: session.country ?? "unknown" },
|
|
704
790
|
{ type: "device", value: input.context.device ?? "unknown" },
|
|
705
791
|
{ type: "browser", value: input.context.browser ?? "Other" },
|
|
706
792
|
{
|
|
@@ -733,6 +819,7 @@ async function accountEvent(
|
|
|
733
819
|
input.batchState,
|
|
734
820
|
);
|
|
735
821
|
}
|
|
822
|
+
return session;
|
|
736
823
|
}
|
|
737
824
|
|
|
738
825
|
async function accountTrustedSession(
|
|
@@ -755,6 +842,10 @@ async function accountTrustedSession(
|
|
|
755
842
|
isNewVisitorDay: false,
|
|
756
843
|
startedAt: existing.startedAt,
|
|
757
844
|
source: existing.source,
|
|
845
|
+
country: existing.country,
|
|
846
|
+
city: existing.city,
|
|
847
|
+
latitude: existing.latitude,
|
|
848
|
+
longitude: existing.longitude,
|
|
758
849
|
pageviewCount: existing.pageviewCount,
|
|
759
850
|
eventCount: existing.eventCount,
|
|
760
851
|
affiliateId: existing.affiliateId,
|
|
@@ -771,6 +862,7 @@ async function upsertSession(
|
|
|
771
862
|
context: IngestContext,
|
|
772
863
|
existing: CachedDocument<"sessions"> | null,
|
|
773
864
|
financialConversion: boolean,
|
|
865
|
+
reportRollupDeltas: ReportRollupDeltas,
|
|
774
866
|
batchState?: BatchState,
|
|
775
867
|
): Promise<SessionState> {
|
|
776
868
|
const source = sourceFromReferrer(event.referrer);
|
|
@@ -785,6 +877,14 @@ async function upsertSession(
|
|
|
785
877
|
batchState,
|
|
786
878
|
);
|
|
787
879
|
if (existing) {
|
|
880
|
+
if (attribution && existing.affiliateId !== attribution.affiliateId) {
|
|
881
|
+
foldAffiliateVisitRollup(
|
|
882
|
+
reportRollupDeltas,
|
|
883
|
+
siteId,
|
|
884
|
+
event,
|
|
885
|
+
attribution,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
788
888
|
const startedAt = Math.min(existing.startedAt, event.timestamp);
|
|
789
889
|
const lastSeenAt = Math.max(existing.lastSeenAt, event.timestamp);
|
|
790
890
|
const updates = {
|
|
@@ -832,6 +932,10 @@ async function upsertSession(
|
|
|
832
932
|
Math.floor(event.timestamp / DAY_MS),
|
|
833
933
|
startedAt,
|
|
834
934
|
source: existing.source,
|
|
935
|
+
country: context.country ?? existing.country,
|
|
936
|
+
city: context.city ?? existing.city,
|
|
937
|
+
latitude: context.latitude ?? existing.latitude,
|
|
938
|
+
longitude: context.longitude ?? existing.longitude,
|
|
835
939
|
pageviewCount:
|
|
836
940
|
existing.pageviewCount + (event.type === "pageview" ? 1 : 0),
|
|
837
941
|
eventCount: existing.eventCount + 1,
|
|
@@ -890,6 +994,14 @@ async function upsertSession(
|
|
|
890
994
|
...(attribution ?? {}),
|
|
891
995
|
};
|
|
892
996
|
const sessionId = await ctx.db.insert("sessions", fields);
|
|
997
|
+
if (attribution) {
|
|
998
|
+
foldAffiliateVisitRollup(
|
|
999
|
+
reportRollupDeltas,
|
|
1000
|
+
siteId,
|
|
1001
|
+
event,
|
|
1002
|
+
attribution,
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
893
1005
|
if (batchState) {
|
|
894
1006
|
const state = batchState.sessions.get(event.sessionId);
|
|
895
1007
|
if (!state) throw new Error("session cache was not initialized");
|
|
@@ -910,6 +1022,10 @@ async function upsertSession(
|
|
|
910
1022
|
Math.floor(event.timestamp / DAY_MS),
|
|
911
1023
|
startedAt: event.timestamp,
|
|
912
1024
|
source,
|
|
1025
|
+
country: context.country,
|
|
1026
|
+
city: context.city,
|
|
1027
|
+
latitude: context.latitude,
|
|
1028
|
+
longitude: context.longitude,
|
|
913
1029
|
pageviewCount: event.type === "pageview" ? 1 : 0,
|
|
914
1030
|
eventCount: 1,
|
|
915
1031
|
...(attribution ?? {}),
|
|
@@ -1102,6 +1218,10 @@ async function accountHeartbeat(
|
|
|
1102
1218
|
isNewVisitorDay: false,
|
|
1103
1219
|
startedAt,
|
|
1104
1220
|
source: session.source,
|
|
1221
|
+
country: context.country ?? session.country,
|
|
1222
|
+
city: context.city ?? session.city,
|
|
1223
|
+
latitude: context.latitude ?? session.latitude,
|
|
1224
|
+
longitude: context.longitude ?? session.longitude,
|
|
1105
1225
|
pageviewCount: session.pageviewCount,
|
|
1106
1226
|
eventCount: session.eventCount,
|
|
1107
1227
|
affiliateSlug: session.affiliateSlug,
|
|
@@ -1115,17 +1235,15 @@ async function consumeSiteRateLimit(
|
|
|
1115
1235
|
ctx: MutationCtx,
|
|
1116
1236
|
siteId: Id<"sites">,
|
|
1117
1237
|
events: TrackerEvent[],
|
|
1238
|
+
eventByteCounts: number[],
|
|
1118
1239
|
now: number,
|
|
1119
|
-
): Promise<
|
|
1240
|
+
): Promise<void> {
|
|
1120
1241
|
const windowStart =
|
|
1121
1242
|
Math.floor(now / RATE_LIMIT_WINDOW_MS) * RATE_LIMIT_WINDOW_MS;
|
|
1122
1243
|
const increments = new Map<number, { eventCount: number; byteCount: number }>();
|
|
1123
|
-
const eventByteCounts: number[] = [];
|
|
1124
|
-
const encoder = new TextEncoder();
|
|
1125
1244
|
for (let index = 0; index < events.length; index += 1) {
|
|
1126
1245
|
const event = events[index];
|
|
1127
|
-
const byteCount =
|
|
1128
|
-
eventByteCounts.push(byteCount);
|
|
1246
|
+
const byteCount = eventByteCounts[index];
|
|
1129
1247
|
const shard = stableHash(event.sessionId) % SITE_INGEST_SHARDS;
|
|
1130
1248
|
const current = increments.get(shard) ?? { eventCount: 0, byteCount: 0 };
|
|
1131
1249
|
current.eventCount += 1;
|
|
@@ -1195,7 +1313,6 @@ async function consumeSiteRateLimit(
|
|
|
1195
1313
|
});
|
|
1196
1314
|
}
|
|
1197
1315
|
}
|
|
1198
|
-
return eventByteCounts;
|
|
1199
1316
|
}
|
|
1200
1317
|
|
|
1201
1318
|
async function consumeRateLimit(
|
|
@@ -1289,6 +1406,10 @@ async function updateLiveSession(
|
|
|
1289
1406
|
)
|
|
1290
1407
|
.unique();
|
|
1291
1408
|
const deadline = now + LIVE_SESSION_TTL_MS;
|
|
1409
|
+
const country = context.country ?? session.country;
|
|
1410
|
+
const city = context.city ?? session.city;
|
|
1411
|
+
const latitude = context.latitude ?? session.latitude;
|
|
1412
|
+
const longitude = context.longitude ?? session.longitude;
|
|
1292
1413
|
if (existing) {
|
|
1293
1414
|
const recentHeartbeatEventIds = event.type === "heartbeat"
|
|
1294
1415
|
? [
|
|
@@ -1304,10 +1425,10 @@ async function updateLiveSession(
|
|
|
1304
1425
|
path: event.path,
|
|
1305
1426
|
lastSeenAt: Math.max(existing.lastSeenAt, event.timestamp),
|
|
1306
1427
|
expiresAt: deadline,
|
|
1307
|
-
...(
|
|
1308
|
-
...(
|
|
1309
|
-
...(
|
|
1310
|
-
...(
|
|
1428
|
+
...(country ? { country } : {}),
|
|
1429
|
+
...(city ? { city } : {}),
|
|
1430
|
+
...(latitude !== undefined ? { latitude } : {}),
|
|
1431
|
+
...(longitude !== undefined ? { longitude } : {}),
|
|
1311
1432
|
browser: context.browser ?? existing.browser,
|
|
1312
1433
|
os: context.os ?? existing.os,
|
|
1313
1434
|
source: session.source,
|
|
@@ -1338,10 +1459,10 @@ async function updateLiveSession(
|
|
|
1338
1459
|
startedAt: event.timestamp,
|
|
1339
1460
|
lastSeenAt: event.timestamp,
|
|
1340
1461
|
expiresAt: deadline,
|
|
1341
|
-
...(
|
|
1342
|
-
...(
|
|
1343
|
-
...(
|
|
1344
|
-
...(
|
|
1462
|
+
...(country ? { country } : {}),
|
|
1463
|
+
...(city ? { city } : {}),
|
|
1464
|
+
...(latitude !== undefined ? { latitude } : {}),
|
|
1465
|
+
...(longitude !== undefined ? { longitude } : {}),
|
|
1345
1466
|
browser: context.browser ?? "Other",
|
|
1346
1467
|
os: context.os ?? "Other",
|
|
1347
1468
|
source: session.source,
|
|
@@ -1376,9 +1497,13 @@ async function ensureLiveSweep(
|
|
|
1376
1497
|
.query("liveSweepStates")
|
|
1377
1498
|
.withIndex("by_siteId", (range) => range.eq("siteId", siteId))
|
|
1378
1499
|
.unique();
|
|
1379
|
-
if (!existing) {
|
|
1500
|
+
if (!existing || existing.scheduledAt <= now) {
|
|
1380
1501
|
const scheduledAt = now + LIVE_SWEEP_INTERVAL_MS;
|
|
1381
|
-
|
|
1502
|
+
if (existing) {
|
|
1503
|
+
await ctx.db.patch("liveSweepStates", existing._id, { scheduledAt });
|
|
1504
|
+
} else {
|
|
1505
|
+
await ctx.db.insert("liveSweepStates", { siteId, scheduledAt });
|
|
1506
|
+
}
|
|
1382
1507
|
await ctx.scheduler.runAt(scheduledAt, internal.live.sweep, {
|
|
1383
1508
|
siteId,
|
|
1384
1509
|
scheduledAt,
|
|
@@ -1399,6 +1524,7 @@ async function advanceFunnel(
|
|
|
1399
1524
|
siteId: Id<"sites">,
|
|
1400
1525
|
funnel: Doc<"funnels">,
|
|
1401
1526
|
event: TrackerEvent,
|
|
1527
|
+
reportRollupDeltas: ReportRollupDeltas,
|
|
1402
1528
|
batchState?: BatchState,
|
|
1403
1529
|
) {
|
|
1404
1530
|
const cacheKey = JSON.stringify([funnel._id, event.visitorId]);
|
|
@@ -1447,6 +1573,7 @@ async function advanceFunnel(
|
|
|
1447
1573
|
};
|
|
1448
1574
|
}
|
|
1449
1575
|
await incrementFunnelStep(ctx, siteId, funnel._id, event, 0, false);
|
|
1576
|
+
foldFunnelRollup(reportRollupDeltas, siteId, funnel._id, event, 0, false);
|
|
1450
1577
|
return;
|
|
1451
1578
|
}
|
|
1452
1579
|
if (progress.completedAt !== undefined || event.timestamp < progress.lastStepAt) return;
|
|
@@ -1473,6 +1600,7 @@ async function advanceFunnel(
|
|
|
1473
1600
|
});
|
|
1474
1601
|
}
|
|
1475
1602
|
await incrementFunnelStep(ctx, siteId, funnel._id, event, 0, false);
|
|
1603
|
+
foldFunnelRollup(reportRollupDeltas, siteId, funnel._id, event, 0, false);
|
|
1476
1604
|
return;
|
|
1477
1605
|
}
|
|
1478
1606
|
const nextStep = funnel.steps[progress.currentStep];
|
|
@@ -1499,6 +1627,14 @@ async function advanceFunnel(
|
|
|
1499
1627
|
progress.currentStep,
|
|
1500
1628
|
completed,
|
|
1501
1629
|
);
|
|
1630
|
+
foldFunnelRollup(
|
|
1631
|
+
reportRollupDeltas,
|
|
1632
|
+
siteId,
|
|
1633
|
+
funnel._id,
|
|
1634
|
+
event,
|
|
1635
|
+
progress.currentStep,
|
|
1636
|
+
completed,
|
|
1637
|
+
);
|
|
1502
1638
|
}
|
|
1503
1639
|
|
|
1504
1640
|
function stepMatches(
|
|
@@ -1557,6 +1693,7 @@ async function accountConversion(
|
|
|
1557
1693
|
session: SessionState,
|
|
1558
1694
|
event: TrackerEvent,
|
|
1559
1695
|
trustedGoalKey: string | undefined,
|
|
1696
|
+
reportRollupDeltas: ReportRollupDeltas,
|
|
1560
1697
|
) {
|
|
1561
1698
|
const revenueCents = event.revenueCents ?? 0;
|
|
1562
1699
|
const currency = event.currency ?? "USD";
|
|
@@ -1588,6 +1725,15 @@ async function accountConversion(
|
|
|
1588
1725
|
commissionCents,
|
|
1589
1726
|
});
|
|
1590
1727
|
if (commissionAffiliate && attribution) {
|
|
1728
|
+
foldAffiliateConversionRollup(
|
|
1729
|
+
reportRollupDeltas,
|
|
1730
|
+
siteId,
|
|
1731
|
+
event,
|
|
1732
|
+
commissionAffiliate,
|
|
1733
|
+
attribution.affiliateSlug,
|
|
1734
|
+
revenueCents,
|
|
1735
|
+
commissionCents,
|
|
1736
|
+
);
|
|
1591
1737
|
const bucketStart = Math.floor(event.timestamp / DAY_MS) * DAY_MS;
|
|
1592
1738
|
const shard = stableHash(event.sessionId) % SECONDARY_AGGREGATE_SHARDS;
|
|
1593
1739
|
const stats = await ctx.db
|
|
@@ -1623,6 +1769,254 @@ async function accountConversion(
|
|
|
1623
1769
|
}
|
|
1624
1770
|
}
|
|
1625
1771
|
|
|
1772
|
+
function createReportRollupDeltas(): ReportRollupDeltas {
|
|
1773
|
+
return {
|
|
1774
|
+
goals: new Map(),
|
|
1775
|
+
funnels: new Map(),
|
|
1776
|
+
affiliates: new Map(),
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function reportRollupCoordinates(
|
|
1781
|
+
siteId: Id<"sites">,
|
|
1782
|
+
definitionId: string,
|
|
1783
|
+
timestamp: number,
|
|
1784
|
+
visitorId: string,
|
|
1785
|
+
) {
|
|
1786
|
+
const bucketStart = Math.floor(timestamp / DAY_MS) * DAY_MS;
|
|
1787
|
+
const shard = stableHash(visitorId) % REPORT_ROLLUP_SHARDS;
|
|
1788
|
+
return {
|
|
1789
|
+
bucketStart,
|
|
1790
|
+
shard,
|
|
1791
|
+
key: `${siteId}:${definitionId}:${bucketStart}:${shard}`,
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function foldGoalRollup(
|
|
1796
|
+
deltas: ReportRollupDeltas,
|
|
1797
|
+
siteId: Id<"sites">,
|
|
1798
|
+
event: TrackerEvent,
|
|
1799
|
+
goal: Doc<"goals">,
|
|
1800
|
+
) {
|
|
1801
|
+
const coordinates = reportRollupCoordinates(
|
|
1802
|
+
siteId,
|
|
1803
|
+
String(goal._id),
|
|
1804
|
+
event.timestamp,
|
|
1805
|
+
event.visitorId,
|
|
1806
|
+
);
|
|
1807
|
+
const current = deltas.goals.get(coordinates.key) ?? {
|
|
1808
|
+
siteId,
|
|
1809
|
+
goalId: goal._id,
|
|
1810
|
+
bucketStart: coordinates.bucketStart,
|
|
1811
|
+
shard: coordinates.shard,
|
|
1812
|
+
goalKey: goal.key,
|
|
1813
|
+
completions: 0,
|
|
1814
|
+
valueCents: 0,
|
|
1815
|
+
};
|
|
1816
|
+
current.completions += 1;
|
|
1817
|
+
current.valueCents += goal.valueCents ?? 0;
|
|
1818
|
+
deltas.goals.set(coordinates.key, current);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function foldFunnelRollup(
|
|
1822
|
+
deltas: ReportRollupDeltas,
|
|
1823
|
+
siteId: Id<"sites">,
|
|
1824
|
+
funnelId: Id<"funnels">,
|
|
1825
|
+
event: TrackerEvent,
|
|
1826
|
+
step: number,
|
|
1827
|
+
completed: boolean,
|
|
1828
|
+
) {
|
|
1829
|
+
const coordinates = reportRollupCoordinates(
|
|
1830
|
+
siteId,
|
|
1831
|
+
String(funnelId),
|
|
1832
|
+
event.timestamp,
|
|
1833
|
+
event.visitorId,
|
|
1834
|
+
);
|
|
1835
|
+
const rollup = deltas.funnels.get(coordinates.key) ?? {
|
|
1836
|
+
siteId,
|
|
1837
|
+
funnelId,
|
|
1838
|
+
bucketStart: coordinates.bucketStart,
|
|
1839
|
+
shard: coordinates.shard,
|
|
1840
|
+
steps: new Map(),
|
|
1841
|
+
};
|
|
1842
|
+
const current = rollup.steps.get(step) ?? {
|
|
1843
|
+
step,
|
|
1844
|
+
entrants: 0,
|
|
1845
|
+
completions: 0,
|
|
1846
|
+
};
|
|
1847
|
+
current.entrants += 1;
|
|
1848
|
+
current.completions += Number(completed);
|
|
1849
|
+
rollup.steps.set(step, current);
|
|
1850
|
+
deltas.funnels.set(coordinates.key, rollup);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
function foldAffiliateVisitRollup(
|
|
1854
|
+
deltas: ReportRollupDeltas,
|
|
1855
|
+
siteId: Id<"sites">,
|
|
1856
|
+
event: TrackerEvent,
|
|
1857
|
+
attribution: AttributionSnapshot,
|
|
1858
|
+
) {
|
|
1859
|
+
const coordinates = reportRollupCoordinates(
|
|
1860
|
+
siteId,
|
|
1861
|
+
String(attribution.affiliateId),
|
|
1862
|
+
event.timestamp,
|
|
1863
|
+
event.visitorId,
|
|
1864
|
+
);
|
|
1865
|
+
const current = deltas.affiliates.get(coordinates.key) ?? {
|
|
1866
|
+
siteId,
|
|
1867
|
+
affiliateId: attribution.affiliateId,
|
|
1868
|
+
bucketStart: coordinates.bucketStart,
|
|
1869
|
+
shard: coordinates.shard,
|
|
1870
|
+
affiliateSlug: attribution.affiliateSlug,
|
|
1871
|
+
visits: 0,
|
|
1872
|
+
visitorSketch: createVisitorSketch(),
|
|
1873
|
+
conversions: 0,
|
|
1874
|
+
revenueCents: 0,
|
|
1875
|
+
commissionCents: 0,
|
|
1876
|
+
};
|
|
1877
|
+
current.visits += 1;
|
|
1878
|
+
current.visitorSketch = addVisitor(current.visitorSketch, event.visitorId);
|
|
1879
|
+
deltas.affiliates.set(coordinates.key, current);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function foldAffiliateConversionRollup(
|
|
1883
|
+
deltas: ReportRollupDeltas,
|
|
1884
|
+
siteId: Id<"sites">,
|
|
1885
|
+
event: TrackerEvent,
|
|
1886
|
+
affiliate: Doc<"affiliates">,
|
|
1887
|
+
affiliateSlug: string,
|
|
1888
|
+
revenueCents: number,
|
|
1889
|
+
commissionCents: number,
|
|
1890
|
+
) {
|
|
1891
|
+
const coordinates = reportRollupCoordinates(
|
|
1892
|
+
siteId,
|
|
1893
|
+
String(affiliate._id),
|
|
1894
|
+
event.timestamp,
|
|
1895
|
+
event.visitorId,
|
|
1896
|
+
);
|
|
1897
|
+
const current = deltas.affiliates.get(coordinates.key) ?? {
|
|
1898
|
+
siteId,
|
|
1899
|
+
affiliateId: affiliate._id,
|
|
1900
|
+
bucketStart: coordinates.bucketStart,
|
|
1901
|
+
shard: coordinates.shard,
|
|
1902
|
+
affiliateSlug,
|
|
1903
|
+
visits: 0,
|
|
1904
|
+
visitorSketch: createVisitorSketch(),
|
|
1905
|
+
conversions: 0,
|
|
1906
|
+
revenueCents: 0,
|
|
1907
|
+
commissionCents: 0,
|
|
1908
|
+
};
|
|
1909
|
+
current.conversions += 1;
|
|
1910
|
+
current.revenueCents += revenueCents;
|
|
1911
|
+
current.commissionCents += commissionCents;
|
|
1912
|
+
deltas.affiliates.set(coordinates.key, current);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
async function flushReportRollups(
|
|
1916
|
+
ctx: MutationCtx,
|
|
1917
|
+
deltas: ReportRollupDeltas,
|
|
1918
|
+
) {
|
|
1919
|
+
for (const delta of deltas.goals.values()) {
|
|
1920
|
+
const existing = await ctx.db
|
|
1921
|
+
.query("goalDailyRollups")
|
|
1922
|
+
.withIndex("by_siteId_and_goalId_and_bucketStart_and_shard", (range) =>
|
|
1923
|
+
range
|
|
1924
|
+
.eq("siteId", delta.siteId)
|
|
1925
|
+
.eq("goalId", delta.goalId)
|
|
1926
|
+
.eq("bucketStart", delta.bucketStart)
|
|
1927
|
+
.eq("shard", delta.shard),
|
|
1928
|
+
)
|
|
1929
|
+
.unique();
|
|
1930
|
+
const fields = {
|
|
1931
|
+
siteId: delta.siteId,
|
|
1932
|
+
goalId: delta.goalId,
|
|
1933
|
+
bucketStart: delta.bucketStart,
|
|
1934
|
+
shard: delta.shard,
|
|
1935
|
+
generation: 1 as const,
|
|
1936
|
+
goalKey: delta.goalKey,
|
|
1937
|
+
completions: (existing?.completions ?? 0) + delta.completions,
|
|
1938
|
+
valueCents: (existing?.valueCents ?? 0) + delta.valueCents,
|
|
1939
|
+
};
|
|
1940
|
+
if (existing) {
|
|
1941
|
+
await ctx.db.replace("goalDailyRollups", existing._id, fields);
|
|
1942
|
+
} else {
|
|
1943
|
+
await ctx.db.insert("goalDailyRollups", fields);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
for (const delta of deltas.funnels.values()) {
|
|
1947
|
+
const existing = await ctx.db
|
|
1948
|
+
.query("funnelDailyRollups")
|
|
1949
|
+
.withIndex("by_siteId_and_funnelId_and_bucketStart_and_shard", (range) =>
|
|
1950
|
+
range
|
|
1951
|
+
.eq("siteId", delta.siteId)
|
|
1952
|
+
.eq("funnelId", delta.funnelId)
|
|
1953
|
+
.eq("bucketStart", delta.bucketStart)
|
|
1954
|
+
.eq("shard", delta.shard),
|
|
1955
|
+
)
|
|
1956
|
+
.unique();
|
|
1957
|
+
const steps = existing?.steps.map((step) => ({ ...step })) ?? [];
|
|
1958
|
+
for (const addition of delta.steps.values()) {
|
|
1959
|
+
const current = steps.find((step) => step.step === addition.step);
|
|
1960
|
+
if (current) {
|
|
1961
|
+
current.entrants += addition.entrants;
|
|
1962
|
+
current.completions += addition.completions;
|
|
1963
|
+
} else {
|
|
1964
|
+
if (steps.length >= MAX_FUNNEL_STEPS) {
|
|
1965
|
+
fail("LIMIT_EXCEEDED", "daily funnel rollup step limit exceeded");
|
|
1966
|
+
}
|
|
1967
|
+
steps.push(addition);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
const fields = {
|
|
1971
|
+
siteId: delta.siteId,
|
|
1972
|
+
funnelId: delta.funnelId,
|
|
1973
|
+
bucketStart: delta.bucketStart,
|
|
1974
|
+
shard: delta.shard,
|
|
1975
|
+
generation: 1 as const,
|
|
1976
|
+
steps,
|
|
1977
|
+
};
|
|
1978
|
+
if (existing) {
|
|
1979
|
+
await ctx.db.replace("funnelDailyRollups", existing._id, fields);
|
|
1980
|
+
} else {
|
|
1981
|
+
await ctx.db.insert("funnelDailyRollups", fields);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
for (const delta of deltas.affiliates.values()) {
|
|
1985
|
+
const existing = await ctx.db
|
|
1986
|
+
.query("affiliateDailyRollups")
|
|
1987
|
+
.withIndex("by_siteId_and_affiliateId_and_bucketStart_and_shard", (range) =>
|
|
1988
|
+
range
|
|
1989
|
+
.eq("siteId", delta.siteId)
|
|
1990
|
+
.eq("affiliateId", delta.affiliateId)
|
|
1991
|
+
.eq("bucketStart", delta.bucketStart)
|
|
1992
|
+
.eq("shard", delta.shard),
|
|
1993
|
+
)
|
|
1994
|
+
.unique();
|
|
1995
|
+
const fields = {
|
|
1996
|
+
siteId: delta.siteId,
|
|
1997
|
+
affiliateId: delta.affiliateId,
|
|
1998
|
+
bucketStart: delta.bucketStart,
|
|
1999
|
+
shard: delta.shard,
|
|
2000
|
+
generation: 1 as const,
|
|
2001
|
+
affiliateSlug: delta.affiliateSlug,
|
|
2002
|
+
visits: (existing?.visits ?? 0) + delta.visits,
|
|
2003
|
+
visitorSketch: unionVisitorSketches(
|
|
2004
|
+
existing?.visitorSketch ?? createVisitorSketch(),
|
|
2005
|
+
delta.visitorSketch,
|
|
2006
|
+
),
|
|
2007
|
+
conversions: (existing?.conversions ?? 0) + delta.conversions,
|
|
2008
|
+
revenueCents: (existing?.revenueCents ?? 0) + delta.revenueCents,
|
|
2009
|
+
commissionCents:
|
|
2010
|
+
(existing?.commissionCents ?? 0) + delta.commissionCents,
|
|
2011
|
+
};
|
|
2012
|
+
if (existing) {
|
|
2013
|
+
await ctx.db.replace("affiliateDailyRollups", existing._id, fields);
|
|
2014
|
+
} else {
|
|
2015
|
+
await ctx.db.insert("affiliateDailyRollups", fields);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
1626
2020
|
function foldAggregates(
|
|
1627
2021
|
aggregateDeltas: AggregateDeltas,
|
|
1628
2022
|
siteId: Id<"sites">,
|
|
@@ -1660,6 +2054,7 @@ function foldAggregates(
|
|
|
1660
2054
|
outboundClicks: 0,
|
|
1661
2055
|
sessions: 0,
|
|
1662
2056
|
visitors: 0,
|
|
2057
|
+
visitorSketch: createVisitorSketch(),
|
|
1663
2058
|
conversions: 0,
|
|
1664
2059
|
revenueCents: 0,
|
|
1665
2060
|
dimensions: [],
|
|
@@ -1680,6 +2075,9 @@ function foldAggregates(
|
|
|
1680
2075
|
? Number(session.isNewVisitorHour)
|
|
1681
2076
|
: Number(session.isNewVisitorDay)
|
|
1682
2077
|
: 0;
|
|
2078
|
+
if (recordTelemetry) {
|
|
2079
|
+
delta.visitorSketch = addVisitor(delta.visitorSketch, event.visitorId);
|
|
2080
|
+
}
|
|
1683
2081
|
delta.conversions += Number(financialConversion);
|
|
1684
2082
|
delta.revenueCents += revenueCents;
|
|
1685
2083
|
delta.dimensions = mergeDimensions(delta.dimensions, dimensionDeltas);
|
|
@@ -1707,6 +2105,13 @@ async function flushAggregates(
|
|
|
1707
2105
|
bucket?.dimensions ?? [],
|
|
1708
2106
|
delta.dimensions,
|
|
1709
2107
|
);
|
|
2108
|
+
const priorSketch = bucket?.visitorSketch;
|
|
2109
|
+
const visitorSketch = priorSketch
|
|
2110
|
+
? unionVisitorSketches(priorSketch, delta.visitorSketch)
|
|
2111
|
+
: delta.visitorSketch;
|
|
2112
|
+
const visitorSketchComplete = bucket
|
|
2113
|
+
? bucket.visitorSketchComplete === true && priorSketch !== undefined
|
|
2114
|
+
: true;
|
|
1710
2115
|
if (bucket) {
|
|
1711
2116
|
await ctx.db.patch("aggregateBuckets", bucket._id, {
|
|
1712
2117
|
events: bucket.events + delta.events,
|
|
@@ -1716,6 +2121,8 @@ async function flushAggregates(
|
|
|
1716
2121
|
outboundClicks: bucket.outboundClicks + delta.outboundClicks,
|
|
1717
2122
|
sessions: bucket.sessions + delta.sessions,
|
|
1718
2123
|
visitors: bucket.visitors + delta.visitors,
|
|
2124
|
+
visitorSketch,
|
|
2125
|
+
visitorSketchComplete,
|
|
1719
2126
|
conversions: bucket.conversions + delta.conversions,
|
|
1720
2127
|
revenueCents: bucket.revenueCents + delta.revenueCents,
|
|
1721
2128
|
dimensions,
|
|
@@ -1733,6 +2140,8 @@ async function flushAggregates(
|
|
|
1733
2140
|
outboundClicks: delta.outboundClicks,
|
|
1734
2141
|
sessions: delta.sessions,
|
|
1735
2142
|
visitors: delta.visitors,
|
|
2143
|
+
visitorSketch,
|
|
2144
|
+
visitorSketchComplete,
|
|
1736
2145
|
conversions: delta.conversions,
|
|
1737
2146
|
revenueCents: delta.revenueCents,
|
|
1738
2147
|
dimensions,
|