@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.
Files changed (179) hide show
  1. package/README.md +693 -69
  2. package/agent/integration.md +801 -0
  3. package/agent/manifest.json +205 -0
  4. package/agent/manifest.schema.json +444 -0
  5. package/dist/client/federation.d.ts +381 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +274 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +2673 -15
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +565 -33
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +22 -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 +315 -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 +28 -1
  29. package/dist/component/constants.d.ts.map +1 -1
  30. package/dist/component/constants.js +44 -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 +27 -0
  36. package/dist/component/coverage.d.ts.map +1 -0
  37. package/dist/component/coverage.js +56 -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 +73 -0
  54. package/dist/component/geo.d.ts.map +1 -0
  55. package/dist/component/geo.js +648 -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 +281 -62
  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 +498 -95
  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/localTime.d.ts +25 -0
  77. package/dist/component/localTime.d.ts.map +1 -0
  78. package/dist/component/localTime.js +126 -0
  79. package/dist/component/localTime.js.map +1 -0
  80. package/dist/component/reports.d.ts +266 -10
  81. package/dist/component/reports.d.ts.map +1 -1
  82. package/dist/component/reports.js +1243 -137
  83. package/dist/component/reports.js.map +1 -1
  84. package/dist/component/retention.d.ts +75 -1
  85. package/dist/component/retention.d.ts.map +1 -1
  86. package/dist/component/retention.js +561 -38
  87. package/dist/component/retention.js.map +1 -1
  88. package/dist/component/rollupStore.d.ts +320 -0
  89. package/dist/component/rollupStore.d.ts.map +1 -0
  90. package/dist/component/rollupStore.js +596 -0
  91. package/dist/component/rollupStore.js.map +1 -0
  92. package/dist/component/rollups.d.ts +20 -0
  93. package/dist/component/rollups.d.ts.map +1 -0
  94. package/dist/component/rollups.js +73 -0
  95. package/dist/component/rollups.js.map +1 -0
  96. package/dist/component/sanitize.d.ts +24 -1
  97. package/dist/component/sanitize.d.ts.map +1 -1
  98. package/dist/component/sanitize.js +96 -16
  99. package/dist/component/sanitize.js.map +1 -1
  100. package/dist/component/schema.d.ts +687 -65
  101. package/dist/component/schema.js +187 -20
  102. package/dist/component/schema.js.map +1 -1
  103. package/dist/component/sites.d.ts +12 -0
  104. package/dist/component/sites.d.ts.map +1 -1
  105. package/dist/component/sites.js +41 -7
  106. package/dist/component/sites.js.map +1 -1
  107. package/dist/component/useragent.d.ts +9 -0
  108. package/dist/component/useragent.d.ts.map +1 -0
  109. package/dist/component/useragent.js +152 -0
  110. package/dist/component/useragent.js.map +1 -0
  111. package/dist/component/validators.d.ts +124 -69
  112. package/dist/component/validators.d.ts.map +1 -1
  113. package/dist/component/validators.js +35 -14
  114. package/dist/component/validators.js.map +1 -1
  115. package/dist/component/visitors.d.ts +19 -0
  116. package/dist/component/visitors.d.ts.map +1 -0
  117. package/dist/component/visitors.js +86 -0
  118. package/dist/component/visitors.js.map +1 -0
  119. package/dist/component/vitals.d.ts +41 -0
  120. package/dist/component/vitals.d.ts.map +1 -0
  121. package/dist/component/vitals.js +115 -0
  122. package/dist/component/vitals.js.map +1 -0
  123. package/dist/react/index.d.ts.map +1 -1
  124. package/dist/react/index.js.map +1 -1
  125. package/dist/tracker/generated.d.ts +11 -4
  126. package/dist/tracker/generated.d.ts.map +1 -1
  127. package/dist/tracker/generated.js +11 -4
  128. package/dist/tracker/generated.js.map +1 -1
  129. package/dist/tracker/tracker.d.ts +1 -1
  130. package/dist/tracker/tracker.d.ts.map +1 -1
  131. package/dist/tracker/tracker.js +57 -20
  132. package/dist/tracker/tracker.js.map +1 -1
  133. package/dist/tracker/vitals.d.ts +10 -0
  134. package/dist/tracker/vitals.d.ts.map +1 -0
  135. package/dist/tracker/vitals.js +140 -0
  136. package/dist/tracker/vitals.js.map +1 -0
  137. package/dist/tracker.min.js +1 -1
  138. package/dist/vitals.min.js +1 -0
  139. package/docs/benchmarks/2026-08-20-realistic.md +76 -76
  140. package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
  141. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  142. package/docs/federation-setup.md +464 -0
  143. package/docs/federation.md +352 -0
  144. package/docs/upgrading.md +344 -0
  145. package/llms.txt +72 -0
  146. package/package.json +55 -11
  147. package/scripts/benchmark-ingest.mjs +175 -73
  148. package/scripts/generate-federation-keys.mjs +20 -0
  149. package/src/component/_generated/api.ts +22 -0
  150. package/src/component/_generated/component.ts +381 -4
  151. package/src/component/_generated/server.ts +4 -0
  152. package/src/component/affiliates.ts +20 -5
  153. package/src/component/cardinality.ts +117 -0
  154. package/src/component/constants.ts +44 -1
  155. package/src/component/convex.config.ts +11 -1
  156. package/src/component/coverage.ts +71 -0
  157. package/src/component/diagnostics.ts +65 -0
  158. package/src/component/errors.ts +2 -1
  159. package/src/component/eventStore.ts +217 -193
  160. package/src/component/funnels.ts +19 -16
  161. package/src/component/geo.ts +835 -0
  162. package/src/component/goals.ts +29 -21
  163. package/src/component/guards.ts +3 -1
  164. package/src/component/http.ts +404 -70
  165. package/src/component/identity.ts +74 -0
  166. package/src/component/ingest.ts +894 -188
  167. package/src/component/live.ts +13 -7
  168. package/src/component/localTime.ts +167 -0
  169. package/src/component/reports.ts +1872 -197
  170. package/src/component/retention.ts +788 -96
  171. package/src/component/rollupStore.ts +799 -0
  172. package/src/component/rollups.ts +82 -0
  173. package/src/component/sanitize.ts +144 -29
  174. package/src/component/schema.ts +217 -21
  175. package/src/component/sites.ts +59 -12
  176. package/src/component/useragent.ts +171 -0
  177. package/src/component/validators.ts +49 -14
  178. package/src/component/visitors.ts +116 -0
  179. package/src/component/vitals.ts +146 -0
@@ -23,16 +23,23 @@ export const expire = internalMutation({
23
23
  if (liveSession.expiryChainDeadline !== args.scheduledDeadline) {
24
24
  return { expired: false, rescheduled: false, stale: true };
25
25
  }
26
- if (liveSession.expiresAt > Date.now()) {
26
+ const now = Date.now();
27
+ if (liveSession.expiresAt > now) {
27
28
  const state = await ctx.db
28
29
  .query("liveSweepStates")
29
- .withIndex("by_siteId", (range) => range.eq("siteId", liveSession.siteId))
30
+ .withIndex("by_siteId", (range) =>
31
+ range.eq("siteId", liveSession.siteId),
32
+ )
30
33
  .unique();
31
34
  const scheduledAt = Math.min(
32
35
  liveSession.expiresAt,
33
- Date.now() + LIVE_SWEEP_INTERVAL_MS,
36
+ now + LIVE_SWEEP_INTERVAL_MS,
34
37
  );
35
- if (!state || state.scheduledAt > scheduledAt) {
38
+ if (
39
+ !state ||
40
+ state.scheduledAt <= now ||
41
+ state.scheduledAt > scheduledAt
42
+ ) {
36
43
  if (state) {
37
44
  await ctx.db.patch("liveSweepStates", state._id, {
38
45
  scheduledAt,
@@ -98,9 +105,8 @@ export const sweep = internalMutation({
98
105
  return { expired: expired.length, rescheduled: false, stale: false };
99
106
  }
100
107
 
101
- const scheduledAt = expired.length === SWEEP_BATCH_SIZE
102
- ? now
103
- : now + LIVE_SWEEP_INTERVAL_MS;
108
+ const scheduledAt =
109
+ expired.length === SWEEP_BATCH_SIZE ? now : now + LIVE_SWEEP_INTERVAL_MS;
104
110
  await ctx.db.patch("liveSweepStates", state._id, { scheduledAt });
105
111
  await ctx.scheduler.runAt(scheduledAt, internal.live.sweep, {
106
112
  siteId: args.siteId,
@@ -0,0 +1,167 @@
1
+ import { DAY_MS, HOUR_MS } from "./constants.js";
2
+
3
+ /**
4
+ * Local calendar days for a site's timezone. Aggregates are bucketed by UTC
5
+ * hour and UTC day; a site with a timezone also gets a `localDay` bucket per
6
+ * calendar day in that zone (see foldAggregates), so "yesterday" in São Paulo
7
+ * ends at midnight in São Paulo, not at 21:00. Every function here uses the
8
+ * runtime's Intl data — the same data `validateTimezone` relies on.
9
+ */
10
+
11
+ const formatters = new Map<string, Intl.DateTimeFormat>();
12
+
13
+ function formatter(timeZone: string): Intl.DateTimeFormat {
14
+ let cached = formatters.get(timeZone);
15
+ if (!cached) {
16
+ cached = new Intl.DateTimeFormat("en-US", {
17
+ timeZone,
18
+ hourCycle: "h23",
19
+ year: "numeric",
20
+ month: "2-digit",
21
+ day: "2-digit",
22
+ hour: "2-digit",
23
+ minute: "2-digit",
24
+ second: "2-digit",
25
+ });
26
+ formatters.set(timeZone, cached);
27
+ }
28
+ return cached;
29
+ }
30
+
31
+ type LocalParts = {
32
+ year: number;
33
+ month: number;
34
+ day: number;
35
+ hour: number;
36
+ minute: number;
37
+ second: number;
38
+ };
39
+
40
+ function localParts(timestamp: number, timeZone: string): LocalParts {
41
+ const parts: Partial<LocalParts> = {};
42
+ for (const part of formatter(timeZone).formatToParts(timestamp)) {
43
+ if (part.type === "literal") continue;
44
+ parts[part.type as keyof LocalParts] = Number(part.value);
45
+ }
46
+ return {
47
+ year: parts.year ?? 1970,
48
+ month: parts.month ?? 1,
49
+ day: parts.day ?? 1,
50
+ // Some engines print 24 for midnight even with hourCycle h23.
51
+ hour: (parts.hour ?? 0) % 24,
52
+ minute: parts.minute ?? 0,
53
+ second: parts.second ?? 0,
54
+ };
55
+ }
56
+
57
+ /** The zone's offset from UTC at `timestamp`, in milliseconds (east is positive). */
58
+ export function localOffsetMs(timestamp: number, timeZone: string): number {
59
+ const parts = localParts(timestamp, timeZone);
60
+ const asUtc = Date.UTC(
61
+ parts.year,
62
+ parts.month - 1,
63
+ parts.day,
64
+ parts.hour,
65
+ parts.minute,
66
+ parts.second,
67
+ );
68
+ return asUtc - (timestamp - (((timestamp % 1000) + 1000) % 1000));
69
+ }
70
+
71
+ function localDateKey(timestamp: number, timeZone: string): number {
72
+ const parts = localParts(timestamp, timeZone);
73
+ return parts.year * 10_000 + parts.month * 100 + parts.day;
74
+ }
75
+
76
+ /**
77
+ * The first instant of the local calendar day that contains `timestamp`, as
78
+ * a UTC millisecond timestamp. Where a transition removes midnight (Chile
79
+ * springs forward at 00:00), the day starts at the first instant that exists.
80
+ */
81
+ export function localDayStart(timestamp: number, timeZone: string): number {
82
+ const parts = localParts(timestamp, timeZone);
83
+ const target = parts.year * 10_000 + parts.month * 100 + parts.day;
84
+ const guess = Date.UTC(parts.year, parts.month - 1, parts.day);
85
+ // The offset in force at UTC midnight is usually the one at local midnight
86
+ // too; around a transition it is not, so a second and third candidate use
87
+ // the offset observed at the previous guess, and the earliest instant that
88
+ // still falls on the target date wins.
89
+ const first = guess - localOffsetMs(guess, timeZone);
90
+ const second = guess - localOffsetMs(first, timeZone);
91
+ const third = guess - localOffsetMs(second, timeZone);
92
+ const candidates = [first, second, third]
93
+ .filter((candidate) => localDateKey(candidate, timeZone) === target)
94
+ .sort((left, right) => left - right);
95
+ return candidates[0] ?? first;
96
+ }
97
+
98
+ /** The start of the local day after the one starting at `dayStart`. */
99
+ export function nextLocalDayStart(dayStart: number, timeZone: string): number {
100
+ return localDayStart(dayStart + DAY_MS + 12 * HOUR_MS, timeZone);
101
+ }
102
+
103
+ /** The start of the local day before the one starting at `dayStart`. */
104
+ export function previousLocalDayStart(
105
+ dayStart: number,
106
+ timeZone: string,
107
+ ): number {
108
+ return localDayStart(dayStart - 12 * HOUR_MS, timeZone);
109
+ }
110
+
111
+ /** Every local day start from `from` through `to`, when `from` is one. */
112
+ export function localDayStarts(
113
+ from: number,
114
+ to: number,
115
+ timeZone: string,
116
+ ): number[] {
117
+ const starts: number[] = [];
118
+ let cursor = from;
119
+ while (cursor <= to) {
120
+ starts.push(cursor);
121
+ cursor = nextLocalDayStart(cursor, timeZone);
122
+ }
123
+ return starts;
124
+ }
125
+
126
+ /** Whether `[from, to]` covers whole local days in the zone, inclusively. */
127
+ export function isLocalDayAligned(
128
+ from: number,
129
+ to: number,
130
+ timeZone: string,
131
+ ): boolean {
132
+ return (
133
+ localDayStart(from, timeZone) === from &&
134
+ localDayStart(to + 1, timeZone) === to + 1
135
+ );
136
+ }
137
+
138
+ /**
139
+ * A site's timezone when it needs its own day buckets: undefined for no
140
+ * timezone and for zones that never leave UTC, whose local days are the UTC
141
+ * days already kept.
142
+ */
143
+ export function localDayTimezone(
144
+ timezone: string | undefined | null,
145
+ ): string | undefined {
146
+ if (!timezone) return undefined;
147
+ try {
148
+ const year = new Date().getUTCFullYear();
149
+ const january = Date.UTC(year, 0, 1);
150
+ const july = Date.UTC(year, 6, 1);
151
+ return localOffsetMs(january, timezone) === 0 &&
152
+ localOffsetMs(july, timezone) === 0
153
+ ? undefined
154
+ : timezone;
155
+ } catch {
156
+ return undefined;
157
+ }
158
+ }
159
+
160
+ /** One timezone shared by every site, or undefined when they differ. */
161
+ export function sharedLocalTimezone(
162
+ timezones: Array<string | undefined>,
163
+ ): string | undefined {
164
+ const distinct = new Set(timezones);
165
+ if (distinct.size !== 1) return undefined;
166
+ return timezones[0];
167
+ }