@iann29/rastro 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +693 -69
- package/agent/integration.md +801 -0
- package/agent/manifest.json +205 -0
- package/agent/manifest.schema.json +444 -0
- package/dist/client/federation.d.ts +381 -0
- package/dist/client/federation.d.ts.map +1 -0
- package/dist/client/federation.js +274 -0
- package/dist/client/federation.js.map +1 -0
- package/dist/client/index.d.ts +2673 -15
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +565 -33
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +22 -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 +315 -4
- 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/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 +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 +28 -1
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +44 -1
- package/dist/component/constants.js.map +1 -1
- package/dist/component/convex.config.d.ts +6 -1
- package/dist/component/convex.config.js +9 -1
- package/dist/component/convex.config.js.map +1 -1
- package/dist/component/coverage.d.ts +27 -0
- package/dist/component/coverage.d.ts.map +1 -0
- package/dist/component/coverage.js +56 -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 +21 -9
- package/dist/component/eventStore.d.ts.map +1 -1
- package/dist/component/eventStore.js +142 -152
- 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 +73 -0
- package/dist/component/geo.d.ts.map +1 -0
- package/dist/component/geo.js +648 -0
- package/dist/component/geo.js.map +1 -0
- 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 +281 -62
- 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 +498 -95
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/live.d.ts.map +1 -1
- package/dist/component/live.js +7 -6
- package/dist/component/live.js.map +1 -1
- package/dist/component/localTime.d.ts +25 -0
- package/dist/component/localTime.d.ts.map +1 -0
- package/dist/component/localTime.js +126 -0
- package/dist/component/localTime.js.map +1 -0
- package/dist/component/reports.d.ts +266 -10
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +1243 -137
- 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 +561 -38
- package/dist/component/retention.js.map +1 -1
- package/dist/component/rollupStore.d.ts +320 -0
- package/dist/component/rollupStore.d.ts.map +1 -0
- package/dist/component/rollupStore.js +596 -0
- package/dist/component/rollupStore.js.map +1 -0
- package/dist/component/rollups.d.ts +20 -0
- package/dist/component/rollups.d.ts.map +1 -0
- package/dist/component/rollups.js +73 -0
- package/dist/component/rollups.js.map +1 -0
- package/dist/component/sanitize.d.ts +24 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +96 -16
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +687 -65
- package/dist/component/schema.js +187 -20
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts +12 -0
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +41 -7
- 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 +124 -69
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +35 -14
- 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 +11 -4
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +11 -4
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.d.ts +1 -1
- package/dist/tracker/tracker.d.ts.map +1 -1
- package/dist/tracker/tracker.js +57 -20
- 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 +140 -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 +76 -76
- package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
- package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
- package/docs/federation-setup.md +464 -0
- package/docs/federation.md +352 -0
- package/docs/upgrading.md +344 -0
- package/llms.txt +72 -0
- package/package.json +55 -11
- package/scripts/benchmark-ingest.mjs +175 -73
- package/scripts/generate-federation-keys.mjs +20 -0
- package/src/component/_generated/api.ts +22 -0
- package/src/component/_generated/component.ts +381 -4
- package/src/component/_generated/server.ts +4 -0
- package/src/component/affiliates.ts +20 -5
- package/src/component/cardinality.ts +117 -0
- package/src/component/constants.ts +44 -1
- package/src/component/convex.config.ts +11 -1
- package/src/component/coverage.ts +71 -0
- package/src/component/diagnostics.ts +65 -0
- package/src/component/errors.ts +2 -1
- package/src/component/eventStore.ts +217 -193
- package/src/component/funnels.ts +19 -16
- package/src/component/geo.ts +835 -0
- package/src/component/goals.ts +29 -21
- package/src/component/guards.ts +3 -1
- package/src/component/http.ts +404 -70
- package/src/component/identity.ts +74 -0
- package/src/component/ingest.ts +894 -188
- package/src/component/live.ts +13 -7
- package/src/component/localTime.ts +167 -0
- package/src/component/reports.ts +1872 -197
- package/src/component/retention.ts +788 -96
- package/src/component/rollupStore.ts +799 -0
- package/src/component/rollups.ts +82 -0
- package/src/component/sanitize.ts +144 -29
- package/src/component/schema.ts +217 -21
- package/src/component/sites.ts +59 -12
- package/src/component/useragent.ts +171 -0
- package/src/component/validators.ts +49 -14
- package/src/component/visitors.ts +116 -0
- package/src/component/vitals.ts +146 -0
|
@@ -7,8 +7,10 @@ export const MAX_BYTES_PER_SESSION_WINDOW = 256 * 1024;
|
|
|
7
7
|
export const SITE_INGEST_SHARDS = 4_096;
|
|
8
8
|
export const MAX_SITE_EVENTS_PER_SHARD_WINDOW = 300;
|
|
9
9
|
export const MAX_SITE_BYTES_PER_SHARD_WINDOW = 512 * 1024;
|
|
10
|
-
export const LIVE_SESSION_TTL_MS =
|
|
10
|
+
export const LIVE_SESSION_TTL_MS = 90_000;
|
|
11
|
+
export const LIVE_LEAVE_GRACE_MS = 10_000;
|
|
11
12
|
export const LIVE_SWEEP_INTERVAL_MS = 5_000;
|
|
13
|
+
export const CLOCK_SKEW_TOLERANCE_MS = 30_000;
|
|
12
14
|
export const AGGREGATE_SHARDS = 128;
|
|
13
15
|
export const HOURLY_AGGREGATE_SHARDS = 256;
|
|
14
16
|
export const SECONDARY_AGGREGATE_SHARDS = 16;
|
|
@@ -24,8 +26,49 @@ export const MAX_REPORT_SITES = 10;
|
|
|
24
26
|
export const MAX_REPORT_RANGE_DAYS = 366;
|
|
25
27
|
export const MAX_LIVE_VISITORS = 500;
|
|
26
28
|
export const MAX_JOURNEY_EVENTS = 500;
|
|
29
|
+
export const JOURNEY_READ_BYTE_RESERVE = 4 * 1024 * 1024;
|
|
30
|
+
export const JOURNEY_READ_DOCUMENT_RESERVE = 1_000;
|
|
27
31
|
export const MAX_RETENTION_BATCH = 500;
|
|
28
32
|
export const MAX_OVERVIEW_AGGREGATE_ROWS = 13_000;
|
|
33
|
+
export const MAX_REPORT_BUCKETS = 5_000;
|
|
34
|
+
export const MAX_FEATURE_ROLLUP_DAYS = 90;
|
|
35
|
+
export const MAX_FEATURE_ROLLUP_ROWS = 5_000;
|
|
36
|
+
export const FEATURE_REPORT_BYTE_RESERVE = 2 * 1024 * 1024;
|
|
37
|
+
export const FEATURE_REPORT_DOCUMENT_RESERVE = 500;
|
|
38
|
+
export const REPORT_ROLLUP_SHARDS = 2;
|
|
39
|
+
export const ROLLUP_SAFETY_DELAY_MS = 5 * 60_000;
|
|
40
|
+
export const RETENTION_LEASE_MS = 60_000;
|
|
41
|
+
// Report rollups are folded by one leased job per site (see rollupStore.ts).
|
|
42
|
+
// A run takes at most this many queued batches, stops early once the merged
|
|
43
|
+
// deltas would touch this many rows, and holds its lease for this long.
|
|
44
|
+
export const ROLLUP_FOLD_BATCH_DOCS = 200;
|
|
45
|
+
export const ROLLUP_FOLD_MAX_ROWS = 1_500;
|
|
46
|
+
export const ROLLUP_FOLD_LEASE_MS = 60_000;
|
|
47
|
+
// Shared Web Vitals histogram edges. Every Google rating threshold (LCP
|
|
48
|
+
// 2500/4000, CLS×1000 100/250, INP 200/500, FCP 1800/3000, TTFB 800/1800) is
|
|
49
|
+
// an edge, so rating counts stay exact while one layout serves all metrics.
|
|
50
|
+
export const VITAL_HISTOGRAM_EDGES = [
|
|
51
|
+
100, 200, 250, 300, 500, 800, 1_000, 1_200, 1_800, 2_500, 3_000, 4_000, 5_000,
|
|
52
|
+
7_500, 10_000,
|
|
53
|
+
];
|
|
54
|
+
export const MAX_VITAL_VALUE = 600_000;
|
|
55
|
+
export const VITAL_PAGE_SLOTS = 48;
|
|
56
|
+
export const MAX_VITALS_REPORT_ROWS = 5_000;
|
|
57
|
+
export const VITALS_REPORT_PAGE_LIMIT = 12;
|
|
29
58
|
|
|
30
59
|
export const DAY_MS = 86_400_000;
|
|
31
60
|
export const HOUR_MS = 3_600_000;
|
|
61
|
+
export const MINUTE_MS = 60_000;
|
|
62
|
+
export const RETENTION_POLICY_INTERVAL_MS = DAY_MS;
|
|
63
|
+
export const MAX_VISITOR_ALIASES = 10;
|
|
64
|
+
// Site map rollups: one row per (site, route, UTC day, shard). A day keeps at
|
|
65
|
+
// most this many route rows per shard before new routes fold into "(other)";
|
|
66
|
+
// each row keeps this many outgoing targets before they fold the same way. A
|
|
67
|
+
// report reads at most MAX_SITE_MAP_ROWS rows, newest day first — about
|
|
68
|
+
// 5 MiB of rows, under the 8 MiB a function may read once the feature-report
|
|
69
|
+
// byte reserve is kept — and declares the older days it could not read as
|
|
70
|
+
// partial coverage: 20 days at the daily cap, 40 for a 100-route site.
|
|
71
|
+
export const MAX_SITE_MAP_ROUTES_PER_DAY = 200;
|
|
72
|
+
export const ROUTE_TRANSITION_SLOTS = 24;
|
|
73
|
+
export const MAX_SITE_MAP_ROWS = 8_000;
|
|
74
|
+
export const SITE_MAP_OTHER_ROUTE = "(other)";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import { defineComponent } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
2
3
|
|
|
3
|
-
export default defineComponent("rastroAnalytics"
|
|
4
|
+
export default defineComponent("rastroAnalytics", {
|
|
5
|
+
env: {
|
|
6
|
+
RASTRO_GEOIP_PROVIDER: v.optional(
|
|
7
|
+
v.union(v.literal("ipinfo"), v.literal("ipwhois")),
|
|
8
|
+
),
|
|
9
|
+
RASTRO_GEOIP_TOKEN: v.optional(v.string()),
|
|
10
|
+
RASTRO_GEOIP_DAILY_LIMIT: v.optional(v.string()),
|
|
11
|
+
RASTRO_TRUST_PROXY: v.optional(v.literal("true")),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Id } from "./_generated/dataModel.js";
|
|
2
|
+
import type { MutationCtx } from "./_generated/server.js";
|
|
3
|
+
import { ROLLUP_SAFETY_DELAY_MS } from "./constants.js";
|
|
4
|
+
|
|
5
|
+
export async function ensureAnalyticsControl(
|
|
6
|
+
ctx: MutationCtx,
|
|
7
|
+
siteId: Id<"sites">,
|
|
8
|
+
now: number,
|
|
9
|
+
retained: Array<{ dataset: string; before: number }> = [],
|
|
10
|
+
) {
|
|
11
|
+
const existing = await ctx.db
|
|
12
|
+
.query("analyticsCoverage")
|
|
13
|
+
.withIndex("by_siteId", (range) => range.eq("siteId", siteId))
|
|
14
|
+
.unique();
|
|
15
|
+
if (existing) {
|
|
16
|
+
// A site that predates a feature starts that feature's coverage at the
|
|
17
|
+
// first ingest after the upgrade, with the same safety delay as rollups:
|
|
18
|
+
// the site map rows, and the alpha.11 counters (pageviews and sessions
|
|
19
|
+
// per dimension slot; duration, continued sessions and new visitors per
|
|
20
|
+
// bucket).
|
|
21
|
+
const stamps: {
|
|
22
|
+
siteMapSince?: number;
|
|
23
|
+
countersSince?: number;
|
|
24
|
+
localDaySince?: number;
|
|
25
|
+
} = {};
|
|
26
|
+
if (existing.siteMapSince === undefined) {
|
|
27
|
+
stamps.siteMapSince = now + ROLLUP_SAFETY_DELAY_MS;
|
|
28
|
+
}
|
|
29
|
+
if (existing.countersSince === undefined) {
|
|
30
|
+
stamps.countersSince = now + ROLLUP_SAFETY_DELAY_MS;
|
|
31
|
+
}
|
|
32
|
+
if (existing.localDaySince === undefined) {
|
|
33
|
+
stamps.localDaySince = now + ROLLUP_SAFETY_DELAY_MS;
|
|
34
|
+
}
|
|
35
|
+
if (Object.keys(stamps).length === 0) return existing;
|
|
36
|
+
await ctx.db.patch("analyticsCoverage", existing._id, {
|
|
37
|
+
...stamps,
|
|
38
|
+
updatedAt: now,
|
|
39
|
+
});
|
|
40
|
+
return { ...existing, ...stamps, updatedAt: now };
|
|
41
|
+
}
|
|
42
|
+
const fields = {
|
|
43
|
+
siteId,
|
|
44
|
+
generation: 1 as const,
|
|
45
|
+
rollupCompleteFrom: now + ROLLUP_SAFETY_DELAY_MS,
|
|
46
|
+
siteMapSince: now + ROLLUP_SAFETY_DELAY_MS,
|
|
47
|
+
countersSince: now + ROLLUP_SAFETY_DELAY_MS,
|
|
48
|
+
localDaySince: now + ROLLUP_SAFETY_DELAY_MS,
|
|
49
|
+
retained,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
};
|
|
52
|
+
const _id = await ctx.db.insert("analyticsCoverage", fields);
|
|
53
|
+
return { ...fields, _id, _creationTime: now };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A site's local-day buckets are computed in its timezone at ingest, so a
|
|
58
|
+
* changed timezone restarts their coverage: rows before the change belong to
|
|
59
|
+
* the old zone's days and must not answer for the new one.
|
|
60
|
+
*/
|
|
61
|
+
export async function resetLocalDayCoverage(
|
|
62
|
+
ctx: MutationCtx,
|
|
63
|
+
siteId: Id<"sites">,
|
|
64
|
+
now: number,
|
|
65
|
+
) {
|
|
66
|
+
const control = await ensureAnalyticsControl(ctx, siteId, now);
|
|
67
|
+
await ctx.db.patch("analyticsCoverage", control._id, {
|
|
68
|
+
localDaySince: now + ROLLUP_SAFETY_DELAY_MS,
|
|
69
|
+
updatedAt: now,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { query } from "./_generated/server.js";
|
|
3
|
+
import { fail } from "./errors.js";
|
|
4
|
+
import { sanitizeOpaqueId } from "./sanitize.js";
|
|
5
|
+
|
|
6
|
+
const MAX_SESSION_TOTAL_IDS = 100;
|
|
7
|
+
|
|
8
|
+
export const sessionEventTotals = query({
|
|
9
|
+
args: {
|
|
10
|
+
siteId: v.id("sites"),
|
|
11
|
+
sessionIds: v.array(v.string()),
|
|
12
|
+
},
|
|
13
|
+
returns: v.object({
|
|
14
|
+
requestedSessions: v.number(),
|
|
15
|
+
foundSessions: v.number(),
|
|
16
|
+
storedEvents: v.number(),
|
|
17
|
+
}),
|
|
18
|
+
handler: async (ctx, args) => {
|
|
19
|
+
if (
|
|
20
|
+
args.sessionIds.length < 1 ||
|
|
21
|
+
args.sessionIds.length > MAX_SESSION_TOTAL_IDS
|
|
22
|
+
) {
|
|
23
|
+
fail(
|
|
24
|
+
"INVALID_ARGUMENT",
|
|
25
|
+
`sessionIds must contain between 1 and ${MAX_SESSION_TOTAL_IDS} IDs`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
let sessionIds: string[];
|
|
29
|
+
try {
|
|
30
|
+
sessionIds = args.sessionIds.map((sessionId) => {
|
|
31
|
+
const sanitized = sanitizeOpaqueId(sessionId, "sessionId");
|
|
32
|
+
if (sanitized !== sessionId)
|
|
33
|
+
throw new Error("sessionId is not canonical");
|
|
34
|
+
return sanitized;
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
fail(
|
|
38
|
+
"INVALID_ARGUMENT",
|
|
39
|
+
error instanceof Error ? error.message : "invalid sessionId",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (new Set(sessionIds).size !== sessionIds.length) {
|
|
43
|
+
fail("INVALID_ARGUMENT", "sessionIds must be unique");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sessions = await Promise.all(
|
|
47
|
+
sessionIds.map((sessionId) =>
|
|
48
|
+
ctx.db
|
|
49
|
+
.query("sessions")
|
|
50
|
+
.withIndex("by_siteId_and_sessionId", (range) =>
|
|
51
|
+
range.eq("siteId", args.siteId).eq("sessionId", sessionId),
|
|
52
|
+
)
|
|
53
|
+
.unique(),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
requestedSessions: sessionIds.length,
|
|
58
|
+
foundSessions: sessions.filter((session) => session !== null).length,
|
|
59
|
+
storedEvents: sessions.reduce(
|
|
60
|
+
(total, session) => total + (session?.eventCount ?? 0),
|
|
61
|
+
0,
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|