@iann29/rastro 0.1.0-alpha.1 → 0.1.0-alpha.10

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.
Files changed (163) hide show
  1. package/README.md +521 -66
  2. package/agent/integration.md +661 -0
  3. package/agent/manifest.json +187 -0
  4. package/agent/manifest.schema.json +411 -0
  5. package/dist/client/federation.d.ts +236 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +196 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +2467 -18
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +461 -32
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +16 -0
  14. package/dist/component/_generated/api.d.ts.map +1 -1
  15. package/dist/component/_generated/api.js.map +1 -1
  16. package/dist/component/_generated/component.d.ts +302 -4
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/_generated/server.d.ts +4 -0
  19. package/dist/component/_generated/server.d.ts.map +1 -1
  20. package/dist/component/_generated/server.js.map +1 -1
  21. package/dist/component/affiliates.d.ts.map +1 -1
  22. package/dist/component/affiliates.js +6 -2
  23. package/dist/component/affiliates.js.map +1 -1
  24. package/dist/component/cardinality.d.ts +12 -0
  25. package/dist/component/cardinality.d.ts.map +1 -0
  26. package/dist/component/cardinality.js +94 -0
  27. package/dist/component/cardinality.js.map +1 -0
  28. package/dist/component/constants.d.ts +25 -1
  29. package/dist/component/constants.d.ts.map +1 -1
  30. package/dist/component/constants.js +38 -1
  31. package/dist/component/constants.js.map +1 -1
  32. package/dist/component/convex.config.d.ts +6 -1
  33. package/dist/component/convex.config.js +9 -1
  34. package/dist/component/convex.config.js.map +1 -1
  35. package/dist/component/coverage.d.ts +19 -0
  36. package/dist/component/coverage.d.ts.map +1 -0
  37. package/dist/component/coverage.js +30 -0
  38. package/dist/component/coverage.js.map +1 -0
  39. package/dist/component/diagnostics.d.ts +9 -0
  40. package/dist/component/diagnostics.d.ts.map +1 -0
  41. package/dist/component/diagnostics.js +47 -0
  42. package/dist/component/diagnostics.js.map +1 -0
  43. package/dist/component/errors.d.ts +1 -1
  44. package/dist/component/errors.d.ts.map +1 -1
  45. package/dist/component/errors.js.map +1 -1
  46. package/dist/component/eventStore.d.ts +21 -9
  47. package/dist/component/eventStore.d.ts.map +1 -1
  48. package/dist/component/eventStore.js +142 -152
  49. package/dist/component/eventStore.js.map +1 -1
  50. package/dist/component/funnels.d.ts.map +1 -1
  51. package/dist/component/funnels.js +5 -3
  52. package/dist/component/funnels.js.map +1 -1
  53. package/dist/component/geo.d.ts +71 -0
  54. package/dist/component/geo.d.ts.map +1 -0
  55. package/dist/component/geo.js +611 -0
  56. package/dist/component/geo.js.map +1 -0
  57. package/dist/component/goals.d.ts.map +1 -1
  58. package/dist/component/goals.js +8 -5
  59. package/dist/component/goals.js.map +1 -1
  60. package/dist/component/guards.d.ts.map +1 -1
  61. package/dist/component/guards.js.map +1 -1
  62. package/dist/component/http.d.ts.map +1 -1
  63. package/dist/component/http.js +248 -49
  64. package/dist/component/http.js.map +1 -1
  65. package/dist/component/identity.d.ts +13 -0
  66. package/dist/component/identity.d.ts.map +1 -0
  67. package/dist/component/identity.js +58 -0
  68. package/dist/component/identity.js.map +1 -0
  69. package/dist/component/ingest.d.ts +3 -1
  70. package/dist/component/ingest.d.ts.map +1 -1
  71. package/dist/component/ingest.js +604 -55
  72. package/dist/component/ingest.js.map +1 -1
  73. package/dist/component/live.d.ts.map +1 -1
  74. package/dist/component/live.js +7 -6
  75. package/dist/component/live.js.map +1 -1
  76. package/dist/component/reports.d.ts +259 -10
  77. package/dist/component/reports.d.ts.map +1 -1
  78. package/dist/component/reports.js +1110 -124
  79. package/dist/component/reports.js.map +1 -1
  80. package/dist/component/retention.d.ts +75 -1
  81. package/dist/component/retention.d.ts.map +1 -1
  82. package/dist/component/retention.js +537 -32
  83. package/dist/component/retention.js.map +1 -1
  84. package/dist/component/sanitize.d.ts +24 -1
  85. package/dist/component/sanitize.d.ts.map +1 -1
  86. package/dist/component/sanitize.js +96 -16
  87. package/dist/component/sanitize.js.map +1 -1
  88. package/dist/component/schema.d.ts +378 -62
  89. package/dist/component/schema.js +178 -20
  90. package/dist/component/schema.js.map +1 -1
  91. package/dist/component/sites.d.ts.map +1 -1
  92. package/dist/component/sites.js +11 -7
  93. package/dist/component/sites.js.map +1 -1
  94. package/dist/component/useragent.d.ts +9 -0
  95. package/dist/component/useragent.d.ts.map +1 -0
  96. package/dist/component/useragent.js +152 -0
  97. package/dist/component/useragent.js.map +1 -0
  98. package/dist/component/validators.d.ts +101 -65
  99. package/dist/component/validators.d.ts.map +1 -1
  100. package/dist/component/validators.js +19 -13
  101. package/dist/component/validators.js.map +1 -1
  102. package/dist/component/visitors.d.ts +19 -0
  103. package/dist/component/visitors.d.ts.map +1 -0
  104. package/dist/component/visitors.js +86 -0
  105. package/dist/component/visitors.js.map +1 -0
  106. package/dist/component/vitals.d.ts +41 -0
  107. package/dist/component/vitals.d.ts.map +1 -0
  108. package/dist/component/vitals.js +115 -0
  109. package/dist/component/vitals.js.map +1 -0
  110. package/dist/react/index.d.ts.map +1 -1
  111. package/dist/react/index.js.map +1 -1
  112. package/dist/tracker/generated.d.ts +8 -4
  113. package/dist/tracker/generated.d.ts.map +1 -1
  114. package/dist/tracker/generated.js +8 -4
  115. package/dist/tracker/generated.js.map +1 -1
  116. package/dist/tracker/tracker.d.ts +1 -1
  117. package/dist/tracker/tracker.d.ts.map +1 -1
  118. package/dist/tracker/tracker.js +57 -20
  119. package/dist/tracker/tracker.js.map +1 -1
  120. package/dist/tracker/vitals.d.ts +10 -0
  121. package/dist/tracker/vitals.d.ts.map +1 -0
  122. package/dist/tracker/vitals.js +140 -0
  123. package/dist/tracker/vitals.js.map +1 -0
  124. package/dist/tracker.min.js +1 -1
  125. package/dist/vitals.min.js +1 -0
  126. package/docs/benchmarks/2026-08-20-realistic.md +76 -76
  127. package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
  128. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  129. package/docs/federation-setup.md +397 -0
  130. package/docs/federation.md +262 -0
  131. package/docs/upgrading.md +259 -0
  132. package/llms.txt +71 -0
  133. package/package.json +55 -11
  134. package/scripts/benchmark-ingest.mjs +175 -73
  135. package/scripts/generate-federation-keys.mjs +20 -0
  136. package/src/component/_generated/api.ts +16 -0
  137. package/src/component/_generated/component.ts +366 -4
  138. package/src/component/_generated/server.ts +4 -0
  139. package/src/component/affiliates.ts +20 -5
  140. package/src/component/cardinality.ts +117 -0
  141. package/src/component/constants.ts +38 -1
  142. package/src/component/convex.config.ts +11 -1
  143. package/src/component/coverage.ts +36 -0
  144. package/src/component/diagnostics.ts +65 -0
  145. package/src/component/errors.ts +2 -1
  146. package/src/component/eventStore.ts +217 -193
  147. package/src/component/funnels.ts +19 -16
  148. package/src/component/geo.ts +781 -0
  149. package/src/component/goals.ts +29 -21
  150. package/src/component/guards.ts +3 -1
  151. package/src/component/http.ts +357 -57
  152. package/src/component/identity.ts +74 -0
  153. package/src/component/ingest.ts +1040 -135
  154. package/src/component/live.ts +13 -7
  155. package/src/component/reports.ts +1675 -183
  156. package/src/component/retention.ts +770 -96
  157. package/src/component/sanitize.ts +144 -29
  158. package/src/component/schema.ts +200 -21
  159. package/src/component/sites.ts +27 -12
  160. package/src/component/useragent.ts +171 -0
  161. package/src/component/validators.ts +29 -13
  162. package/src/component/visitors.ts +116 -0
  163. 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 = 60_000;
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,43 @@ 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
+ // Shared Web Vitals histogram edges. Every Google rating threshold (LCP
42
+ // 2500/4000, CLS×1000 100/250, INP 200/500, FCP 1800/3000, TTFB 800/1800) is
43
+ // an edge, so rating counts stay exact while one layout serves all metrics.
44
+ export const VITAL_HISTOGRAM_EDGES = [
45
+ 100, 200, 250, 300, 500, 800, 1_000, 1_200, 1_800, 2_500, 3_000, 4_000, 5_000,
46
+ 7_500, 10_000,
47
+ ];
48
+ export const MAX_VITAL_VALUE = 600_000;
49
+ export const VITAL_PAGE_SLOTS = 48;
50
+ export const MAX_VITALS_REPORT_ROWS = 5_000;
51
+ export const VITALS_REPORT_PAGE_LIMIT = 12;
29
52
 
30
53
  export const DAY_MS = 86_400_000;
31
54
  export const HOUR_MS = 3_600_000;
55
+ export const MINUTE_MS = 60_000;
56
+ export const RETENTION_POLICY_INTERVAL_MS = DAY_MS;
57
+ export const MAX_VISITOR_ALIASES = 10;
58
+ // Site map rollups: one row per (site, route, UTC day, shard). A day keeps at
59
+ // most this many route rows per shard before new routes fold into "(other)";
60
+ // each row keeps this many outgoing targets before they fold the same way. A
61
+ // report reads at most MAX_SITE_MAP_ROWS rows, newest day first — about
62
+ // 5 MiB of rows, under the 8 MiB a function may read once the feature-report
63
+ // byte reserve is kept — and declares the older days it could not read as
64
+ // partial coverage: 20 days at the daily cap, 40 for a 100-route site.
65
+ export const MAX_SITE_MAP_ROUTES_PER_DAY = 200;
66
+ export const ROUTE_TRANSITION_SLOTS = 24;
67
+ export const MAX_SITE_MAP_ROWS = 8_000;
68
+ 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,36 @@
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
+ if (existing.siteMapSince !== undefined) return existing;
17
+ // A site that predates the site map starts its map coverage at the first
18
+ // ingest after the upgrade; the same safety delay applies as for rollups.
19
+ const siteMapSince = now + ROLLUP_SAFETY_DELAY_MS;
20
+ await ctx.db.patch("analyticsCoverage", existing._id, {
21
+ siteMapSince,
22
+ updatedAt: now,
23
+ });
24
+ return { ...existing, siteMapSince, updatedAt: now };
25
+ }
26
+ const fields = {
27
+ siteId,
28
+ generation: 1 as const,
29
+ rollupCompleteFrom: now + ROLLUP_SAFETY_DELAY_MS,
30
+ siteMapSince: now + ROLLUP_SAFETY_DELAY_MS,
31
+ retained,
32
+ updatedAt: now,
33
+ };
34
+ const _id = await ctx.db.insert("analyticsCoverage", fields);
35
+ return { ...fields, _id, _creationTime: now };
36
+ }
@@ -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
+ });
@@ -9,7 +9,8 @@ export type RastroErrorCode =
9
9
  | "LIMIT_EXCEEDED"
10
10
  | "CONFLICT"
11
11
  | "CURRENCY_MISMATCH"
12
- | "RATE_LIMITED";
12
+ | "RATE_LIMITED"
13
+ | "REPORT_INCOMPLETE";
13
14
 
14
15
  export function fail(
15
16
  code: RastroErrorCode,