@iann29/rastro 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +79 -30
- package/dist/component/_generated/api.d.ts +5 -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 +10 -0
- 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/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/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/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 +124 -11
- package/dist/component/http.js.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +34 -14
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/reports.d.ts +8 -8
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +2 -1
- package/dist/component/reports.js.map +1 -1
- package/dist/component/schema.d.ts +15 -52
- package/dist/component/schema.js +14 -16
- package/dist/component/schema.js.map +1 -1
- package/dist/component/validators.d.ts +3 -50
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +4 -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 +1 -1
- package/package.json +2 -6
- package/scripts/benchmark-ingest.mjs +81 -32
- package/src/component/_generated/api.ts +5 -5
- package/src/component/_generated/component.ts +13 -0
- package/src/component/_generated/server.ts +4 -0
- package/src/component/convex.config.ts +11 -5
- package/src/component/diagnostics.ts +64 -0
- package/src/component/eventStore.ts +78 -280
- package/src/component/geo.ts +779 -0
- package/src/component/http.ts +184 -17
- package/src/component/ingest.ts +49 -15
- package/src/component/reports.ts +4 -1
- package/src/component/schema.ts +15 -16
- package/src/component/validators.ts +4 -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
|
@@ -3,7 +3,6 @@ import type { MutationCtx, QueryCtx } from "./_generated/server.js";
|
|
|
3
3
|
import {
|
|
4
4
|
JOURNEY_READ_BYTE_RESERVE,
|
|
5
5
|
JOURNEY_READ_DOCUMENT_RESERVE,
|
|
6
|
-
MAX_BATCH_EVENTS,
|
|
7
6
|
MAX_EVENTS_PER_SESSION_WINDOW,
|
|
8
7
|
RATE_LIMIT_WINDOW_MS,
|
|
9
8
|
} from "./constants.js";
|
|
@@ -32,23 +31,22 @@ type BufferedEventBatch = {
|
|
|
32
31
|
|
|
33
32
|
export type EventBatchBuffer = Map<string, BufferedEventBatch>;
|
|
34
33
|
|
|
35
|
-
type MigrationBatch = Pick<
|
|
36
|
-
Doc<"eventBatches">,
|
|
37
|
-
"_id" | "visitorId" | "events" | "migrationVersion"
|
|
38
|
-
>;
|
|
39
|
-
|
|
40
|
-
const migrationBatchCache = new WeakMap<
|
|
41
|
-
MutationCtx,
|
|
42
|
-
Map<string, MigrationBatch[]>
|
|
43
|
-
>();
|
|
44
|
-
|
|
45
34
|
export type JourneyCursor = {
|
|
46
35
|
timestamp: number;
|
|
47
36
|
creationTime: number;
|
|
48
37
|
id: string;
|
|
49
38
|
};
|
|
50
39
|
|
|
51
|
-
export type RawJourneyEvent = Omit<
|
|
40
|
+
export type RawJourneyEvent = Omit<
|
|
41
|
+
Doc<"eventBatches">["events"][number],
|
|
42
|
+
"aggregateCountry"
|
|
43
|
+
> & {
|
|
44
|
+
_id: string;
|
|
45
|
+
_creationTime: number;
|
|
46
|
+
siteId: Id<"sites">;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
visitorId: string;
|
|
49
|
+
};
|
|
52
50
|
|
|
53
51
|
export function eventBucketStart(timestamp: number): number {
|
|
54
52
|
return Math.floor(timestamp / RATE_LIMIT_WINDOW_MS) * RATE_LIMIT_WINDOW_MS;
|
|
@@ -64,32 +62,15 @@ export async function inspectTelemetryMinute(
|
|
|
64
62
|
siteId: Id<"sites">;
|
|
65
63
|
sessionId: string;
|
|
66
64
|
bucketStart: number;
|
|
67
|
-
candidateEventIds: Set<string>;
|
|
68
65
|
},
|
|
69
66
|
) {
|
|
70
|
-
const
|
|
71
|
-
const eventIds = new Set(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
batch.events.map((event) => event.eventId),
|
|
75
|
-
),
|
|
76
|
-
]);
|
|
77
|
-
if (legacyEvents.length > MAX_EVENTS_PER_SESSION_WINDOW) {
|
|
78
|
-
await Promise.all(
|
|
79
|
-
[...input.candidateEventIds]
|
|
80
|
-
.filter((eventId) => !eventIds.has(eventId))
|
|
81
|
-
.map(async (eventId) => {
|
|
82
|
-
const legacy = await findLegacyTelemetry(ctx, {
|
|
83
|
-
...input,
|
|
84
|
-
eventId,
|
|
85
|
-
});
|
|
86
|
-
if (legacy) eventIds.add(eventId);
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
67
|
+
const batches = await loadTelemetryMinute(ctx, input);
|
|
68
|
+
const eventIds = new Set(
|
|
69
|
+
batches.flatMap((batch) => batch.events.map((event) => event.eventId)),
|
|
70
|
+
);
|
|
90
71
|
return {
|
|
91
72
|
eventIds,
|
|
92
|
-
persistedCount: telemetryMinuteCount(
|
|
73
|
+
persistedCount: telemetryMinuteCount(batches),
|
|
93
74
|
};
|
|
94
75
|
}
|
|
95
76
|
|
|
@@ -99,28 +80,13 @@ export async function findTelemetry(
|
|
|
99
80
|
event: TrackerEvent,
|
|
100
81
|
): Promise<{ match: TelemetryMatch | null; saturated: boolean }> {
|
|
101
82
|
const bucketStart = eventBucketStart(event.timestamp);
|
|
102
|
-
const
|
|
83
|
+
const batches = await loadTelemetryMinute(ctx, {
|
|
103
84
|
siteId,
|
|
104
85
|
sessionId: event.sessionId,
|
|
105
86
|
bucketStart,
|
|
106
87
|
});
|
|
107
|
-
const persistedCount = telemetryMinuteCount(
|
|
108
|
-
let legacyEvent = legacyEvents.find((candidate) =>
|
|
109
|
-
candidate.eventId === event.eventId
|
|
110
|
-
);
|
|
111
|
-
if (
|
|
112
|
-
!legacyEvent &&
|
|
113
|
-
legacyEvents.length > MAX_EVENTS_PER_SESSION_WINDOW
|
|
114
|
-
) {
|
|
115
|
-
legacyEvent = await findLegacyTelemetry(ctx, {
|
|
116
|
-
siteId,
|
|
117
|
-
sessionId: event.sessionId,
|
|
118
|
-
bucketStart,
|
|
119
|
-
eventId: event.eventId,
|
|
120
|
-
}) ?? undefined;
|
|
121
|
-
}
|
|
88
|
+
const persistedCount = telemetryMinuteCount(batches);
|
|
122
89
|
const saturated = persistedCount >= MAX_EVENTS_PER_SESSION_WINDOW;
|
|
123
|
-
if (legacyEvent) return { match: legacyEvent, saturated };
|
|
124
90
|
let batch: Doc<"eventBatches"> | undefined;
|
|
125
91
|
let nestedEvent: Doc<"eventBatches">["events"][number] | undefined;
|
|
126
92
|
for (const candidate of batches) {
|
|
@@ -148,6 +114,7 @@ export function bufferEventBatch(
|
|
|
148
114
|
siteId: Id<"sites">,
|
|
149
115
|
event: TrackerEvent,
|
|
150
116
|
buffer: EventBatchBuffer,
|
|
117
|
+
country: string,
|
|
151
118
|
) {
|
|
152
119
|
const bucketStart = eventBucketStart(event.timestamp);
|
|
153
120
|
const key = eventBatchKey(event.sessionId, bucketStart);
|
|
@@ -156,7 +123,7 @@ export function bufferEventBatch(
|
|
|
156
123
|
if (existing.visitorId !== event.visitorId) {
|
|
157
124
|
throw new Error("event batch visitor does not match its session");
|
|
158
125
|
}
|
|
159
|
-
existing.events.push(eventForBatch(event));
|
|
126
|
+
existing.events.push(eventForBatch(event, country));
|
|
160
127
|
return;
|
|
161
128
|
}
|
|
162
129
|
buffer.set(key, {
|
|
@@ -164,7 +131,7 @@ export function bufferEventBatch(
|
|
|
164
131
|
sessionId: event.sessionId,
|
|
165
132
|
visitorId: event.visitorId,
|
|
166
133
|
bucketStart,
|
|
167
|
-
events: [eventForBatch(event)],
|
|
134
|
+
events: [eventForBatch(event, country)],
|
|
168
135
|
});
|
|
169
136
|
}
|
|
170
137
|
|
|
@@ -172,13 +139,14 @@ export async function insertEventBatch(
|
|
|
172
139
|
ctx: MutationCtx,
|
|
173
140
|
siteId: Id<"sites">,
|
|
174
141
|
event: TrackerEvent,
|
|
142
|
+
country: string,
|
|
175
143
|
) {
|
|
176
144
|
await ctx.db.insert("eventBatches", {
|
|
177
145
|
siteId,
|
|
178
146
|
sessionId: event.sessionId,
|
|
179
147
|
visitorId: event.visitorId,
|
|
180
148
|
bucketStart: eventBucketStart(event.timestamp),
|
|
181
|
-
events: [eventForBatch(event)],
|
|
149
|
+
events: [eventForBatch(event, country)],
|
|
182
150
|
});
|
|
183
151
|
}
|
|
184
152
|
|
|
@@ -191,84 +159,6 @@ export async function flushEventBatches(
|
|
|
191
159
|
}
|
|
192
160
|
}
|
|
193
161
|
|
|
194
|
-
export async function migrateLegacyEventToBatch(
|
|
195
|
-
ctx: MutationCtx,
|
|
196
|
-
event: Doc<"events">,
|
|
197
|
-
) {
|
|
198
|
-
const bucketStart = eventBucketStart(event.timestamp);
|
|
199
|
-
let transactionCache = migrationBatchCache.get(ctx);
|
|
200
|
-
if (!transactionCache) {
|
|
201
|
-
transactionCache = new Map();
|
|
202
|
-
migrationBatchCache.set(ctx, transactionCache);
|
|
203
|
-
}
|
|
204
|
-
const cacheKey = JSON.stringify([
|
|
205
|
-
event.siteId,
|
|
206
|
-
event.sessionId,
|
|
207
|
-
bucketStart,
|
|
208
|
-
]);
|
|
209
|
-
let batches = transactionCache.get(cacheKey);
|
|
210
|
-
if (!batches) {
|
|
211
|
-
batches = await ctx.db
|
|
212
|
-
.query("eventBatches")
|
|
213
|
-
.withIndex("by_siteId_and_sessionId_and_bucketStart", (range) =>
|
|
214
|
-
range
|
|
215
|
-
.eq("siteId", event.siteId)
|
|
216
|
-
.eq("sessionId", event.sessionId)
|
|
217
|
-
.eq("bucketStart", bucketStart),
|
|
218
|
-
)
|
|
219
|
-
.order("desc")
|
|
220
|
-
.take(MAX_EVENTS_PER_SESSION_WINDOW + 1);
|
|
221
|
-
transactionCache.set(cacheKey, batches);
|
|
222
|
-
}
|
|
223
|
-
const duplicateBatch = batches.find((batch) =>
|
|
224
|
-
batch.events.some((candidate) => candidate.eventId === event.eventId)
|
|
225
|
-
);
|
|
226
|
-
const context = eventContext(event);
|
|
227
|
-
if (duplicateBatch) {
|
|
228
|
-
if (Object.keys(context).length > 0) {
|
|
229
|
-
const events = duplicateBatch.events.map((candidate) =>
|
|
230
|
-
candidate.eventId === event.eventId
|
|
231
|
-
? { ...candidate, ...context }
|
|
232
|
-
: candidate
|
|
233
|
-
);
|
|
234
|
-
await ctx.db.patch("eventBatches", duplicateBatch._id, {
|
|
235
|
-
events,
|
|
236
|
-
});
|
|
237
|
-
duplicateBatch.events = events;
|
|
238
|
-
}
|
|
239
|
-
await ctx.db.delete("events", event._id);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const migratedEvent = {
|
|
243
|
-
...eventForBatch(event),
|
|
244
|
-
...context,
|
|
245
|
-
};
|
|
246
|
-
const latestBatch = batches[0];
|
|
247
|
-
if (
|
|
248
|
-
latestBatch?.migrationVersion === 1 &&
|
|
249
|
-
latestBatch.visitorId === event.visitorId &&
|
|
250
|
-
latestBatch.events.length < MAX_BATCH_EVENTS
|
|
251
|
-
) {
|
|
252
|
-
const events = [...latestBatch.events, migratedEvent];
|
|
253
|
-
await ctx.db.patch("eventBatches", latestBatch._id, {
|
|
254
|
-
events,
|
|
255
|
-
});
|
|
256
|
-
latestBatch.events = events;
|
|
257
|
-
} else {
|
|
258
|
-
const batch = {
|
|
259
|
-
siteId: event.siteId,
|
|
260
|
-
sessionId: event.sessionId,
|
|
261
|
-
visitorId: event.visitorId,
|
|
262
|
-
bucketStart,
|
|
263
|
-
events: [migratedEvent],
|
|
264
|
-
migrationVersion: 1 as const,
|
|
265
|
-
};
|
|
266
|
-
const batchId = await ctx.db.insert("eventBatches", batch);
|
|
267
|
-
batches.unshift({ _id: batchId, ...batch });
|
|
268
|
-
}
|
|
269
|
-
await ctx.db.delete("events", event._id);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
162
|
export async function loadSessionJourneyEvents(
|
|
273
163
|
ctx: QueryCtx,
|
|
274
164
|
input: {
|
|
@@ -287,8 +177,7 @@ export async function loadSessionJourneyEvents(
|
|
|
287
177
|
// be read completely before applying the cursor.
|
|
288
178
|
const loadBatches = async () => {
|
|
289
179
|
const batches: Doc<"eventBatches">[] = [];
|
|
290
|
-
let
|
|
291
|
-
const requiredEvents = input.resultLimit + MAX_EVENTS_PER_SESSION_WINDOW;
|
|
180
|
+
let qualifyingEvents = 0;
|
|
292
181
|
let completedBucket: number | undefined;
|
|
293
182
|
const query = ctx.db
|
|
294
183
|
.query("eventBatches")
|
|
@@ -305,60 +194,23 @@ export async function loadSessionJourneyEvents(
|
|
|
305
194
|
break;
|
|
306
195
|
}
|
|
307
196
|
batches.push(batch);
|
|
308
|
-
|
|
309
|
-
|
|
197
|
+
qualifyingEvents += batch.events.reduce((count, _event, index) => {
|
|
198
|
+
const event = eventFromBatch(batch, index);
|
|
199
|
+
return count + Number(
|
|
200
|
+
event.timestamp >= input.from &&
|
|
201
|
+
event.timestamp <= input.to &&
|
|
202
|
+
(!input.cursor ||
|
|
203
|
+
compareJourneyEventToCursor(event, input.cursor) > 0),
|
|
204
|
+
);
|
|
205
|
+
}, 0);
|
|
206
|
+
if (qualifyingEvents >= input.resultLimit) {
|
|
207
|
+
completedBucket = batch.bucketStart;
|
|
208
|
+
}
|
|
310
209
|
}
|
|
311
210
|
return batches;
|
|
312
211
|
};
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
if (!input.cursor) {
|
|
316
|
-
return ctx.db
|
|
317
|
-
.query("events")
|
|
318
|
-
.withIndex("by_siteId_and_sessionId_and_timestamp", (range) =>
|
|
319
|
-
range
|
|
320
|
-
.eq("siteId", input.siteId)
|
|
321
|
-
.eq("sessionId", input.sessionId)
|
|
322
|
-
.gte("timestamp", input.from)
|
|
323
|
-
.lte("timestamp", input.to),
|
|
324
|
-
)
|
|
325
|
-
.order("asc")
|
|
326
|
-
.take(take);
|
|
327
|
-
}
|
|
328
|
-
const rows = await ctx.db
|
|
329
|
-
.query("events")
|
|
330
|
-
.withIndex("by_siteId_and_sessionId_and_timestamp", (range) =>
|
|
331
|
-
range
|
|
332
|
-
.eq("siteId", input.siteId)
|
|
333
|
-
.eq("sessionId", input.sessionId)
|
|
334
|
-
.eq("timestamp", input.cursor!.timestamp)
|
|
335
|
-
.gt("_creationTime", input.cursor!.creationTime),
|
|
336
|
-
)
|
|
337
|
-
.order("asc")
|
|
338
|
-
.take(take);
|
|
339
|
-
if (rows.length < take) {
|
|
340
|
-
rows.push(...await ctx.db
|
|
341
|
-
.query("events")
|
|
342
|
-
.withIndex("by_siteId_and_sessionId_and_timestamp", (range) =>
|
|
343
|
-
range
|
|
344
|
-
.eq("siteId", input.siteId)
|
|
345
|
-
.eq("sessionId", input.sessionId)
|
|
346
|
-
.gt("timestamp", input.cursor!.timestamp)
|
|
347
|
-
.lte("timestamp", input.to),
|
|
348
|
-
)
|
|
349
|
-
.order("asc")
|
|
350
|
-
.take(take - rows.length));
|
|
351
|
-
}
|
|
352
|
-
return rows;
|
|
353
|
-
};
|
|
354
|
-
const [legacyEvents, batches] = await Promise.all([
|
|
355
|
-
loadLegacyEvents(),
|
|
356
|
-
loadBatches(),
|
|
357
|
-
]);
|
|
358
|
-
const rows: RawJourneyEvent[] = legacyEvents.map((event) => ({
|
|
359
|
-
...event,
|
|
360
|
-
_id: String(event._id),
|
|
361
|
-
}));
|
|
212
|
+
const batches = await loadBatches();
|
|
213
|
+
const rows: RawJourneyEvent[] = [];
|
|
362
214
|
appendBatchEvents(rows, batches);
|
|
363
215
|
return rows
|
|
364
216
|
.filter((event) =>
|
|
@@ -411,27 +263,8 @@ export async function loadVisitorJourneyEvents(
|
|
|
411
263
|
}
|
|
412
264
|
return batches;
|
|
413
265
|
};
|
|
414
|
-
const legacyEvents: Doc<"events">[] = [];
|
|
415
|
-
const legacyQuery = ctx.db
|
|
416
|
-
.query("events")
|
|
417
|
-
.withIndex("by_siteId_and_visitorId_and_timestamp", (range) =>
|
|
418
|
-
range
|
|
419
|
-
.eq("siteId", input.siteId)
|
|
420
|
-
.eq("visitorId", input.visitorId)
|
|
421
|
-
.gte("timestamp", input.from)
|
|
422
|
-
.lte("timestamp", input.to),
|
|
423
|
-
)
|
|
424
|
-
.order("asc");
|
|
425
|
-
for await (const event of legacyQuery) {
|
|
426
|
-
legacyEvents.push(event);
|
|
427
|
-
await requireJourneyReadHeadroom(ctx);
|
|
428
|
-
if (legacyEvents.length >= input.resultLimit) break;
|
|
429
|
-
}
|
|
430
266
|
const batches = await loadBatches();
|
|
431
|
-
const rows: RawJourneyEvent[] =
|
|
432
|
-
...event,
|
|
433
|
-
_id: String(event._id),
|
|
434
|
-
}));
|
|
267
|
+
const rows: RawJourneyEvent[] = [];
|
|
435
268
|
appendBatchEvents(rows, batches);
|
|
436
269
|
return rows
|
|
437
270
|
.filter((event) =>
|
|
@@ -505,32 +338,19 @@ export async function deleteTelemetryBefore(
|
|
|
505
338
|
) {
|
|
506
339
|
const batchCutoff = eventBucketStart(input.before);
|
|
507
340
|
const limit = input.batchSize + 1;
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
ctx.db
|
|
516
|
-
.query("eventBatches")
|
|
517
|
-
.withIndex("by_siteId_and_bucketStart", (range) =>
|
|
518
|
-
range.eq("siteId", input.siteId).lt("bucketStart", batchCutoff),
|
|
519
|
-
)
|
|
520
|
-
.take(limit),
|
|
521
|
-
]);
|
|
522
|
-
const rows = [...legacyEvents, ...batches].slice(0, input.batchSize);
|
|
341
|
+
const batches = await ctx.db
|
|
342
|
+
.query("eventBatches")
|
|
343
|
+
.withIndex("by_siteId_and_bucketStart", (range) =>
|
|
344
|
+
range.eq("siteId", input.siteId).lt("bucketStart", batchCutoff),
|
|
345
|
+
)
|
|
346
|
+
.take(limit);
|
|
347
|
+
const rows = batches.slice(0, input.batchSize);
|
|
523
348
|
for (const row of rows) {
|
|
524
|
-
|
|
525
|
-
await ctx.db.delete("eventBatches", row._id);
|
|
526
|
-
} else {
|
|
527
|
-
await ctx.db.delete("events", row._id);
|
|
528
|
-
}
|
|
349
|
+
await ctx.db.delete("eventBatches", row._id);
|
|
529
350
|
}
|
|
530
351
|
return {
|
|
531
352
|
deleted: rows.length,
|
|
532
|
-
hasMore:
|
|
533
|
-
legacyEvents.length + batches.length > input.batchSize,
|
|
353
|
+
hasMore: batches.length > input.batchSize,
|
|
534
354
|
};
|
|
535
355
|
}
|
|
536
356
|
|
|
@@ -542,61 +362,30 @@ async function loadTelemetryMinute(
|
|
|
542
362
|
bucketStart: number;
|
|
543
363
|
},
|
|
544
364
|
) {
|
|
545
|
-
return
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
.gte("timestamp", input.bucketStart)
|
|
553
|
-
.lt("timestamp", input.bucketStart + RATE_LIMIT_WINDOW_MS),
|
|
554
|
-
)
|
|
555
|
-
.take(MAX_EVENTS_PER_SESSION_WINDOW + 1),
|
|
556
|
-
ctx.db
|
|
557
|
-
.query("eventBatches")
|
|
558
|
-
.withIndex("by_siteId_and_sessionId_and_bucketStart", (range) =>
|
|
559
|
-
range
|
|
560
|
-
.eq("siteId", input.siteId)
|
|
561
|
-
.eq("sessionId", input.sessionId)
|
|
562
|
-
.eq("bucketStart", input.bucketStart),
|
|
563
|
-
)
|
|
564
|
-
.take(MAX_EVENTS_PER_SESSION_WINDOW + 1),
|
|
565
|
-
]);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
async function findLegacyTelemetry(
|
|
569
|
-
ctx: MutationCtx,
|
|
570
|
-
input: {
|
|
571
|
-
siteId: Id<"sites">;
|
|
572
|
-
sessionId: string;
|
|
573
|
-
bucketStart: number;
|
|
574
|
-
eventId: string;
|
|
575
|
-
},
|
|
576
|
-
) {
|
|
577
|
-
const candidates = await ctx.db
|
|
578
|
-
.query("events")
|
|
579
|
-
.withIndex("by_siteId_and_eventId", (range) =>
|
|
580
|
-
range.eq("siteId", input.siteId).eq("eventId", input.eventId),
|
|
365
|
+
return ctx.db
|
|
366
|
+
.query("eventBatches")
|
|
367
|
+
.withIndex("by_siteId_and_sessionId_and_bucketStart", (range) =>
|
|
368
|
+
range
|
|
369
|
+
.eq("siteId", input.siteId)
|
|
370
|
+
.eq("sessionId", input.sessionId)
|
|
371
|
+
.eq("bucketStart", input.bucketStart),
|
|
581
372
|
)
|
|
582
|
-
.take(
|
|
583
|
-
return candidates.find((candidate) =>
|
|
584
|
-
candidate.sessionId === input.sessionId &&
|
|
585
|
-
eventBucketStart(candidate.timestamp) === input.bucketStart
|
|
586
|
-
) ?? null;
|
|
373
|
+
.take(MAX_EVENTS_PER_SESSION_WINDOW + 1);
|
|
587
374
|
}
|
|
588
375
|
|
|
589
376
|
function telemetryMinuteCount(
|
|
590
|
-
legacyEvents: Array<{ eventId: string }>,
|
|
591
377
|
batches: Array<{ events: TrackerEvent[] }>,
|
|
592
378
|
) {
|
|
593
|
-
return
|
|
379
|
+
return batches.reduce(
|
|
594
380
|
(total, batch) => total + batch.events.length,
|
|
595
381
|
0,
|
|
596
382
|
);
|
|
597
383
|
}
|
|
598
384
|
|
|
599
|
-
function eventForBatch(
|
|
385
|
+
function eventForBatch(
|
|
386
|
+
event: TrackerEvent,
|
|
387
|
+
country: string,
|
|
388
|
+
): TrackerEvent & { aggregateCountry?: string } {
|
|
600
389
|
return {
|
|
601
390
|
eventId: event.eventId,
|
|
602
391
|
sessionId: event.sessionId,
|
|
@@ -615,6 +404,7 @@ function eventForBatch(event: TrackerEvent): TrackerEvent {
|
|
|
615
404
|
: {}),
|
|
616
405
|
...(event.currency ? { currency: event.currency } : {}),
|
|
617
406
|
...(event.affiliateSlug ? { affiliateSlug: event.affiliateSlug } : {}),
|
|
407
|
+
aggregateCountry: country,
|
|
618
408
|
};
|
|
619
409
|
}
|
|
620
410
|
|
|
@@ -624,19 +414,27 @@ function appendBatchEvents(
|
|
|
624
414
|
) {
|
|
625
415
|
for (const batch of batches) {
|
|
626
416
|
for (let index = 0; index < batch.events.length; index += 1) {
|
|
627
|
-
|
|
628
|
-
rows.push({
|
|
629
|
-
...event,
|
|
630
|
-
_id: `eventBatch:${batch._id}:${index.toString().padStart(3, "0")}`,
|
|
631
|
-
_creationTime: batch._creationTime,
|
|
632
|
-
siteId: batch.siteId,
|
|
633
|
-
sessionId: batch.sessionId,
|
|
634
|
-
visitorId: batch.visitorId,
|
|
635
|
-
});
|
|
417
|
+
rows.push(eventFromBatch(batch, index));
|
|
636
418
|
}
|
|
637
419
|
}
|
|
638
420
|
}
|
|
639
421
|
|
|
422
|
+
function eventFromBatch(
|
|
423
|
+
batch: Doc<"eventBatches">,
|
|
424
|
+
index: number,
|
|
425
|
+
): RawJourneyEvent {
|
|
426
|
+
const { aggregateCountry: _aggregateCountry, ...event } = batch.events[index]!;
|
|
427
|
+
void _aggregateCountry;
|
|
428
|
+
return {
|
|
429
|
+
...event,
|
|
430
|
+
_id: `eventBatch:${batch._id}:${index.toString().padStart(3, "0")}`,
|
|
431
|
+
_creationTime: batch._creationTime,
|
|
432
|
+
siteId: batch.siteId,
|
|
433
|
+
sessionId: batch.sessionId,
|
|
434
|
+
visitorId: batch.visitorId,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
640
438
|
function eventContext(event: {
|
|
641
439
|
country?: string;
|
|
642
440
|
city?: string;
|