@iann29/rastro 0.1.0-alpha.0 → 0.1.0-alpha.1
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 +85 -18
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/constants.d.ts +9 -6
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +9 -6
- package/dist/component/constants.js.map +1 -1
- package/dist/component/eventStore.d.ts +94 -0
- package/dist/component/eventStore.d.ts.map +1 -0
- package/dist/component/eventStore.js +304 -0
- package/dist/component/eventStore.js.map +1 -0
- package/dist/component/goals.d.ts.map +1 -1
- package/dist/component/goals.js +17 -4
- package/dist/component/goals.js.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +39 -7
- package/dist/component/http.js.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +520 -207
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/live.d.ts +8 -0
- package/dist/component/live.d.ts.map +1 -1
- package/dist/component/live.js +72 -8
- package/dist/component/live.js.map +1 -1
- package/dist/component/reports.d.ts +16 -16
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +250 -66
- package/dist/component/reports.js.map +1 -1
- package/dist/component/retention.d.ts.map +1 -1
- package/dist/component/retention.js +15 -10
- package/dist/component/retention.js.map +1 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +11 -3
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +101 -10
- package/dist/component/schema.js +30 -8
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +27 -18
- package/dist/component/sites.js.map +1 -1
- package/dist/component/validators.d.ts +49 -0
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +11 -0
- 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 +33 -22
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/benchmarks/2026-08-20-realistic.md +171 -0
- package/package.json +10 -4
- package/scripts/benchmark-ingest.mjs +499 -0
- package/src/component/_generated/api.ts +2 -0
- package/src/component/constants.ts +9 -6
- package/src/component/eventStore.ts +465 -0
- package/src/component/goals.ts +25 -4
- package/src/component/http.ts +49 -6
- package/src/component/ingest.ts +671 -215
- package/src/component/live.ts +83 -8
- package/src/component/reports.ts +320 -82
- package/src/component/retention.ts +15 -12
- package/src/component/sanitize.ts +10 -3
- package/src/component/schema.ts +33 -8
- package/src/component/sites.ts +31 -18
- package/src/component/validators.ts +12 -0
|
@@ -152,12 +152,19 @@ export function sanitizeEvent(event: TrackerEvent, now: number): TrackerEvent {
|
|
|
152
152
|
properties: sanitizeProperties(event.properties),
|
|
153
153
|
revenueCents: event.revenueCents,
|
|
154
154
|
currency,
|
|
155
|
-
affiliateSlug: event.affiliateSlug
|
|
156
|
-
? undefined
|
|
157
|
-
: sanitizeSlug(event.affiliateSlug, "affiliateSlug"),
|
|
155
|
+
affiliateSlug: optionalAffiliateSlug(event.affiliateSlug),
|
|
158
156
|
};
|
|
159
157
|
}
|
|
160
158
|
|
|
159
|
+
function optionalAffiliateSlug(value: string | undefined): string | undefined {
|
|
160
|
+
if (value === undefined) return undefined;
|
|
161
|
+
try {
|
|
162
|
+
return sanitizeSlug(value, "affiliateSlug");
|
|
163
|
+
} catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
161
168
|
export function normalizeDomains(domains: string[]): string[] {
|
|
162
169
|
if (domains.length === 0 || domains.length > MAX_SITE_DOMAINS) {
|
|
163
170
|
throw new Error(`domains must contain between 1 and ${MAX_SITE_DOMAINS} entries`);
|
package/src/component/schema.ts
CHANGED
|
@@ -3,11 +3,12 @@ import { v } from "convex/values";
|
|
|
3
3
|
import {
|
|
4
4
|
affiliateFieldsValidator,
|
|
5
5
|
aggregateFieldsValidator,
|
|
6
|
-
|
|
6
|
+
storedEventFieldsValidator,
|
|
7
7
|
funnelFieldsValidator,
|
|
8
8
|
goalFieldsValidator,
|
|
9
9
|
sessionFieldsValidator,
|
|
10
10
|
siteFieldsValidator,
|
|
11
|
+
trackerEventValidator,
|
|
11
12
|
} from "./validators.js";
|
|
12
13
|
|
|
13
14
|
export default defineSchema({
|
|
@@ -45,26 +46,49 @@ export default defineSchema({
|
|
|
45
46
|
device: v.string(),
|
|
46
47
|
pageviewCount: v.number(),
|
|
47
48
|
lastHeartbeatEventId: v.optional(v.string()),
|
|
49
|
+
recentHeartbeatEventIds: v.optional(v.array(v.string())),
|
|
48
50
|
eventCount: v.number(),
|
|
49
51
|
})
|
|
50
52
|
.index("by_siteId_and_sessionId", ["siteId", "sessionId"])
|
|
51
53
|
.index("by_siteId_and_expiresAt", ["siteId", "expiresAt"])
|
|
52
|
-
.index("by_siteId_and_lastSeenAt", ["siteId", "lastSeenAt"])
|
|
54
|
+
.index("by_siteId_and_lastSeenAt", ["siteId", "lastSeenAt"])
|
|
55
|
+
.index("by_expiresAt", { fields: ["expiresAt"], staged: true }),
|
|
56
|
+
|
|
57
|
+
liveSweepStates: defineTable({
|
|
58
|
+
siteId: v.id("sites"),
|
|
59
|
+
scheduledAt: v.number(),
|
|
60
|
+
}).index("by_siteId", ["siteId"]),
|
|
53
61
|
|
|
54
|
-
events: defineTable(
|
|
62
|
+
events: defineTable(storedEventFieldsValidator)
|
|
55
63
|
.index("by_siteId_and_eventId", ["siteId", "eventId"])
|
|
56
64
|
.index("by_siteId_and_timestamp", ["siteId", "timestamp"])
|
|
57
|
-
.index("
|
|
65
|
+
.index("by_siteId_and_sessionId_and_timestamp", [
|
|
58
66
|
"siteId",
|
|
59
|
-
"
|
|
67
|
+
"sessionId",
|
|
60
68
|
"timestamp",
|
|
61
69
|
])
|
|
62
|
-
.index("
|
|
70
|
+
.index("by_siteId_and_visitorId_and_timestamp", {
|
|
71
|
+
fields: ["siteId", "visitorId", "timestamp"],
|
|
72
|
+
staged: true,
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
eventBatches: defineTable({
|
|
76
|
+
siteId: v.id("sites"),
|
|
77
|
+
sessionId: v.string(),
|
|
78
|
+
visitorId: v.string(),
|
|
79
|
+
bucketStart: v.number(),
|
|
80
|
+
events: v.array(trackerEventValidator),
|
|
81
|
+
})
|
|
82
|
+
.index("by_siteId_and_sessionId_and_bucketStart", [
|
|
63
83
|
"siteId",
|
|
64
84
|
"sessionId",
|
|
65
|
-
"
|
|
85
|
+
"bucketStart",
|
|
66
86
|
])
|
|
67
|
-
.index("
|
|
87
|
+
.index("by_siteId_and_bucketStart", ["siteId", "bucketStart"])
|
|
88
|
+
.index("by_siteId_and_visitorId_and_bucketStart", {
|
|
89
|
+
fields: ["siteId", "visitorId", "bucketStart"],
|
|
90
|
+
staged: true,
|
|
91
|
+
}),
|
|
68
92
|
|
|
69
93
|
aggregateBuckets: defineTable(aggregateFieldsValidator)
|
|
70
94
|
.index("by_siteId_and_granularity_and_bucketStart_and_shard", [
|
|
@@ -88,6 +112,7 @@ export default defineSchema({
|
|
|
88
112
|
goalId: v.id("goals"),
|
|
89
113
|
goalKey: v.string(),
|
|
90
114
|
eventId: v.string(),
|
|
115
|
+
idempotencyVersion: v.optional(v.literal(2)),
|
|
91
116
|
sessionId: v.string(),
|
|
92
117
|
visitorId: v.string(),
|
|
93
118
|
timestamp: v.number(),
|
package/src/component/sites.ts
CHANGED
|
@@ -33,13 +33,41 @@ export const create = mutation({
|
|
|
33
33
|
const name = cleanString(args.name, 120);
|
|
34
34
|
if (!ownerId || !name) fail("INVALID_SITE", "ownerId and name are required");
|
|
35
35
|
|
|
36
|
+
let domains: string[];
|
|
37
|
+
let timezone: string | undefined;
|
|
38
|
+
let networkId: string | undefined;
|
|
39
|
+
let currency: string;
|
|
40
|
+
try {
|
|
41
|
+
domains = normalizeDomains(args.domains);
|
|
42
|
+
timezone = validateTimezone(args.timezone);
|
|
43
|
+
networkId = args.networkId
|
|
44
|
+
? sanitizeOpaqueId(args.networkId, "networkId")
|
|
45
|
+
: undefined;
|
|
46
|
+
currency = sanitizeCurrency(args.currency ?? "USD");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
fail("INVALID_SITE", error instanceof Error ? error.message : "invalid site");
|
|
49
|
+
}
|
|
50
|
+
const cookieless = args.cookieless ?? true;
|
|
51
|
+
|
|
36
52
|
const existing = await ctx.db
|
|
37
53
|
.query("sites")
|
|
38
54
|
.withIndex("by_ownerId_and_name", (range) =>
|
|
39
55
|
range.eq("ownerId", ownerId).eq("name", name),
|
|
40
56
|
)
|
|
41
57
|
.unique();
|
|
42
|
-
if (existing)
|
|
58
|
+
if (existing) {
|
|
59
|
+
if (
|
|
60
|
+
existing.domains.length !== domains.length ||
|
|
61
|
+
existing.domains.some((domain, index) => domain !== domains[index]) ||
|
|
62
|
+
existing.networkId !== networkId ||
|
|
63
|
+
existing.timezone !== timezone ||
|
|
64
|
+
(existing.currency ?? "USD") !== currency ||
|
|
65
|
+
existing.cookieless !== cookieless
|
|
66
|
+
) {
|
|
67
|
+
fail("CONFLICT", "site name is already associated with different settings");
|
|
68
|
+
}
|
|
69
|
+
return existing._id;
|
|
70
|
+
}
|
|
43
71
|
|
|
44
72
|
const sites = await ctx.db
|
|
45
73
|
.query("sites")
|
|
@@ -51,20 +79,6 @@ export const create = mutation({
|
|
|
51
79
|
});
|
|
52
80
|
}
|
|
53
81
|
|
|
54
|
-
let domains: string[];
|
|
55
|
-
let timezone: string | undefined;
|
|
56
|
-
let networkId: string | undefined;
|
|
57
|
-
let currency: string;
|
|
58
|
-
try {
|
|
59
|
-
domains = normalizeDomains(args.domains);
|
|
60
|
-
timezone = validateTimezone(args.timezone);
|
|
61
|
-
networkId = args.networkId
|
|
62
|
-
? sanitizeOpaqueId(args.networkId, "networkId")
|
|
63
|
-
: undefined;
|
|
64
|
-
currency = sanitizeCurrency(args.currency ?? "USD");
|
|
65
|
-
} catch (error) {
|
|
66
|
-
fail("INVALID_SITE", error instanceof Error ? error.message : "invalid site");
|
|
67
|
-
}
|
|
68
82
|
const now = Date.now();
|
|
69
83
|
return await ctx.db.insert("sites", {
|
|
70
84
|
ownerId,
|
|
@@ -73,7 +87,7 @@ export const create = mutation({
|
|
|
73
87
|
networkId,
|
|
74
88
|
timezone,
|
|
75
89
|
currency,
|
|
76
|
-
cookieless
|
|
90
|
+
cookieless,
|
|
77
91
|
createdAt: now,
|
|
78
92
|
updatedAt: now,
|
|
79
93
|
});
|
|
@@ -138,8 +152,7 @@ export const update = mutation({
|
|
|
138
152
|
}
|
|
139
153
|
if (
|
|
140
154
|
patch.currency !== undefined &&
|
|
141
|
-
site.currency !==
|
|
142
|
-
site.currency !== patch.currency
|
|
155
|
+
(site.currency ?? "USD") !== patch.currency
|
|
143
156
|
) {
|
|
144
157
|
fail("CONFLICT", "site currency cannot be changed");
|
|
145
158
|
}
|
|
@@ -119,6 +119,18 @@ export const eventFieldsValidator = v.object({
|
|
|
119
119
|
device: v.string(),
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
export const storedEventFieldsValidator = eventFieldsValidator
|
|
123
|
+
.omit("country", "city", "latitude", "longitude", "browser", "os", "device")
|
|
124
|
+
.extend({
|
|
125
|
+
country: v.optional(v.string()),
|
|
126
|
+
city: v.optional(v.string()),
|
|
127
|
+
latitude: v.optional(v.number()),
|
|
128
|
+
longitude: v.optional(v.number()),
|
|
129
|
+
browser: v.optional(v.string()),
|
|
130
|
+
os: v.optional(v.string()),
|
|
131
|
+
device: v.optional(v.string()),
|
|
132
|
+
});
|
|
133
|
+
|
|
122
134
|
export const dimensionTypeValidator = v.union(
|
|
123
135
|
v.literal("source"),
|
|
124
136
|
v.literal("page"),
|