@iann29/rastro 0.1.0-alpha.3 → 0.1.0-alpha.4

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 (86) hide show
  1. package/README.md +264 -51
  2. package/agent/integration.md +651 -0
  3. package/agent/manifest.json +183 -0
  4. package/agent/manifest.schema.json +405 -0
  5. package/dist/client/federation.d.ts +205 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +179 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +1600 -8
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +210 -2
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +4 -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 +151 -1
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/cardinality.d.ts +12 -0
  19. package/dist/component/cardinality.d.ts.map +1 -0
  20. package/dist/component/cardinality.js +94 -0
  21. package/dist/component/cardinality.js.map +1 -0
  22. package/dist/component/constants.d.ts +10 -0
  23. package/dist/component/constants.d.ts.map +1 -1
  24. package/dist/component/constants.js +10 -0
  25. package/dist/component/constants.js.map +1 -1
  26. package/dist/component/coverage.d.ts +18 -0
  27. package/dist/component/coverage.d.ts.map +1 -0
  28. package/dist/component/coverage.js +19 -0
  29. package/dist/component/coverage.js.map +1 -0
  30. package/dist/component/errors.d.ts +1 -1
  31. package/dist/component/errors.d.ts.map +1 -1
  32. package/dist/component/errors.js.map +1 -1
  33. package/dist/component/http.d.ts.map +1 -1
  34. package/dist/component/http.js +46 -8
  35. package/dist/component/http.js.map +1 -1
  36. package/dist/component/ingest.d.ts.map +1 -1
  37. package/dist/component/ingest.js +238 -18
  38. package/dist/component/ingest.js.map +1 -1
  39. package/dist/component/live.d.ts.map +1 -1
  40. package/dist/component/live.js +6 -3
  41. package/dist/component/live.js.map +1 -1
  42. package/dist/component/reports.d.ts +111 -2
  43. package/dist/component/reports.d.ts.map +1 -1
  44. package/dist/component/reports.js +639 -78
  45. package/dist/component/reports.js.map +1 -1
  46. package/dist/component/retention.d.ts +75 -1
  47. package/dist/component/retention.d.ts.map +1 -1
  48. package/dist/component/retention.js +517 -54
  49. package/dist/component/retention.js.map +1 -1
  50. package/dist/component/sanitize.d.ts +4 -1
  51. package/dist/component/sanitize.d.ts.map +1 -1
  52. package/dist/component/sanitize.js +9 -4
  53. package/dist/component/sanitize.js.map +1 -1
  54. package/dist/component/schema.d.ts +187 -1
  55. package/dist/component/schema.js +100 -1
  56. package/dist/component/schema.js.map +1 -1
  57. package/dist/component/sites.d.ts.map +1 -1
  58. package/dist/component/sites.js +5 -1
  59. package/dist/component/sites.js.map +1 -1
  60. package/dist/component/validators.d.ts +26 -1
  61. package/dist/component/validators.d.ts.map +1 -1
  62. package/dist/component/validators.js +7 -0
  63. package/dist/component/validators.js.map +1 -1
  64. package/docs/benchmarks/2026-08-20-realistic.md +4 -4
  65. package/docs/benchmarks/2026-08-21-formal-certification.md +333 -0
  66. package/docs/federation-setup.md +395 -0
  67. package/docs/federation.md +258 -0
  68. package/docs/upgrading.md +130 -0
  69. package/llms.txt +65 -0
  70. package/package.json +32 -6
  71. package/scripts/generate-federation-keys.mjs +20 -0
  72. package/src/component/_generated/api.ts +4 -0
  73. package/src/component/_generated/component.ts +211 -1
  74. package/src/component/cardinality.ts +116 -0
  75. package/src/component/constants.ts +10 -0
  76. package/src/component/coverage.ts +25 -0
  77. package/src/component/errors.ts +2 -1
  78. package/src/component/http.ts +76 -6
  79. package/src/component/ingest.ts +387 -12
  80. package/src/component/live.ts +8 -3
  81. package/src/component/reports.ts +891 -86
  82. package/src/component/retention.ts +624 -88
  83. package/src/component/sanitize.ts +15 -4
  84. package/src/component/schema.ts +110 -0
  85. package/src/component/sites.ts +5 -1
  86. package/src/component/validators.ts +8 -0
@@ -249,10 +249,21 @@ export function isValidMoney(value: number): boolean {
249
249
  return Number.isSafeInteger(value) && value >= 0 && value <= 1_000_000_000;
250
250
  }
251
251
 
252
- export function ensureBatchBytes(value: unknown): number {
253
- const bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
254
- if (bytes > MAX_BATCH_BYTES) throw new Error("batch exceeds 64 KiB");
255
- return bytes;
252
+ export function measureBatchBytes(
253
+ siteId: string,
254
+ events: TrackerEvent[],
255
+ ): { batchBytes: number; eventByteCounts: number[] } {
256
+ const encoder = new TextEncoder();
257
+ const eventByteCounts = events.map((event) =>
258
+ encoder.encode(JSON.stringify(event)).byteLength
259
+ );
260
+ // The empty envelope already includes both array brackets; only commas remain.
261
+ const batchBytes =
262
+ encoder.encode(JSON.stringify({ siteId, events: [] })).byteLength +
263
+ eventByteCounts.reduce((total, byteCount) => total + byteCount, 0) +
264
+ Math.max(0, events.length - 1);
265
+ if (batchBytes > MAX_BATCH_BYTES) throw new Error("batch exceeds 64 KiB");
266
+ return { batchBytes, eventByteCounts };
256
267
  }
257
268
 
258
269
  export function sourceFromReferrer(referrer: string | undefined): string {
@@ -8,6 +8,7 @@ import {
8
8
  goalFieldsValidator,
9
9
  sessionFieldsValidator,
10
10
  siteFieldsValidator,
11
+ visitorSketchValidator,
11
12
  } from "./validators.js";
12
13
 
13
14
  export default defineSchema({
@@ -101,6 +102,115 @@ export default defineSchema({
101
102
  "bucketStart",
102
103
  ]),
103
104
 
105
+ goalDailyRollups: defineTable({
106
+ siteId: v.id("sites"),
107
+ goalId: v.id("goals"),
108
+ bucketStart: v.number(),
109
+ shard: v.number(),
110
+ generation: v.literal(1),
111
+ goalKey: v.string(),
112
+ completions: v.number(),
113
+ valueCents: v.number(),
114
+ })
115
+ .index("by_siteId_and_goalId_and_bucketStart_and_shard", [
116
+ "siteId",
117
+ "goalId",
118
+ "bucketStart",
119
+ "shard",
120
+ ])
121
+ .index("by_siteId_and_goalId_and_bucketStart", [
122
+ "siteId",
123
+ "goalId",
124
+ "bucketStart",
125
+ ])
126
+ .index("by_siteId_and_bucketStart", ["siteId", "bucketStart"]),
127
+
128
+ funnelDailyRollups: defineTable({
129
+ siteId: v.id("sites"),
130
+ funnelId: v.id("funnels"),
131
+ bucketStart: v.number(),
132
+ shard: v.number(),
133
+ generation: v.literal(1),
134
+ steps: v.array(v.object({
135
+ step: v.number(),
136
+ entrants: v.number(),
137
+ completions: v.number(),
138
+ })),
139
+ })
140
+ .index("by_siteId_and_funnelId_and_bucketStart_and_shard", [
141
+ "siteId",
142
+ "funnelId",
143
+ "bucketStart",
144
+ "shard",
145
+ ])
146
+ .index("by_siteId_and_funnelId_and_bucketStart", [
147
+ "siteId",
148
+ "funnelId",
149
+ "bucketStart",
150
+ ])
151
+ .index("by_siteId_and_bucketStart", ["siteId", "bucketStart"]),
152
+
153
+ affiliateDailyRollups: defineTable({
154
+ siteId: v.id("sites"),
155
+ affiliateId: v.id("affiliates"),
156
+ bucketStart: v.number(),
157
+ shard: v.number(),
158
+ generation: v.literal(1),
159
+ affiliateSlug: v.string(),
160
+ visits: v.number(),
161
+ visitorSketch: visitorSketchValidator,
162
+ conversions: v.number(),
163
+ revenueCents: v.number(),
164
+ commissionCents: v.number(),
165
+ })
166
+ .index("by_siteId_and_affiliateId_and_bucketStart_and_shard", [
167
+ "siteId",
168
+ "affiliateId",
169
+ "bucketStart",
170
+ "shard",
171
+ ])
172
+ .index("by_siteId_and_affiliateId_and_bucketStart", [
173
+ "siteId",
174
+ "affiliateId",
175
+ "bucketStart",
176
+ ])
177
+ .index("by_siteId_and_bucketStart", ["siteId", "bucketStart"]),
178
+
179
+ analyticsCoverage: defineTable({
180
+ siteId: v.id("sites"),
181
+ generation: v.literal(1),
182
+ rollupCompleteFrom: v.number(),
183
+ retained: v.array(v.object({ dataset: v.string(), before: v.number() })),
184
+ updatedAt: v.number(),
185
+ }).index("by_siteId", ["siteId"]),
186
+
187
+ retentionPolicies: defineTable({
188
+ siteId: v.id("sites"),
189
+ eventsDays: v.number(),
190
+ sessionsDays: v.number(),
191
+ conversionsDays: v.number(),
192
+ aggregatesDays: v.number(),
193
+ reportsDays: v.number(),
194
+ updatedAt: v.number(),
195
+ }).index("by_siteId", ["siteId"]),
196
+
197
+ retentionStates: defineTable({
198
+ siteId: v.id("sites"),
199
+ kind: v.string(),
200
+ before: v.number(),
201
+ status: v.union(
202
+ v.literal("pending"),
203
+ v.literal("running"),
204
+ v.literal("complete"),
205
+ ),
206
+ generation: v.number(),
207
+ attempt: v.number(),
208
+ leaseExpiresAt: v.optional(v.number()),
209
+ scheduledAt: v.optional(v.number()),
210
+ deleted: v.number(),
211
+ updatedAt: v.number(),
212
+ }).index("by_siteId_and_kind", ["siteId", "kind"]),
213
+
104
214
  goals: defineTable(goalFieldsValidator)
105
215
  .index("by_siteId", ["siteId"])
106
216
  .index("by_siteId_and_key", ["siteId", "key"]),
@@ -1,6 +1,7 @@
1
1
  import { v } from "convex/values";
2
2
  import { internalQuery, mutation, query } from "./_generated/server.js";
3
3
  import { MAX_SITES_PER_OWNER } from "./constants.js";
4
+ import { ensureAnalyticsControl } from "./coverage.js";
4
5
  import { fail } from "./errors.js";
5
6
  import {
6
7
  cleanString,
@@ -66,6 +67,7 @@ export const create = mutation({
66
67
  ) {
67
68
  fail("CONFLICT", "site name is already associated with different settings");
68
69
  }
70
+ await ensureAnalyticsControl(ctx, existing._id, Date.now());
69
71
  return existing._id;
70
72
  }
71
73
 
@@ -80,7 +82,7 @@ export const create = mutation({
80
82
  }
81
83
 
82
84
  const now = Date.now();
83
- return await ctx.db.insert("sites", {
85
+ const siteId = await ctx.db.insert("sites", {
84
86
  ownerId,
85
87
  name,
86
88
  domains,
@@ -91,6 +93,8 @@ export const create = mutation({
91
93
  createdAt: now,
92
94
  updatedAt: now,
93
95
  });
96
+ await ensureAnalyticsControl(ctx, siteId, now);
97
+ return siteId;
94
98
  },
95
99
  });
96
100
 
@@ -144,6 +144,12 @@ export const dimensionSlotValidator = v.object({
144
144
  revenueCents: v.number(),
145
145
  });
146
146
 
147
+ export const visitorSketchValidator = v.object({
148
+ version: v.literal(1),
149
+ precision: v.number(),
150
+ registers: v.bytes(),
151
+ });
152
+
147
153
  export const aggregateFieldsValidator = v.object({
148
154
  siteId: v.id("sites"),
149
155
  granularity: v.union(v.literal("hour"), v.literal("day")),
@@ -156,6 +162,8 @@ export const aggregateFieldsValidator = v.object({
156
162
  outboundClicks: v.number(),
157
163
  sessions: v.number(),
158
164
  visitors: v.number(),
165
+ visitorSketch: v.optional(visitorSketchValidator),
166
+ visitorSketchComplete: v.optional(v.boolean()),
159
167
  conversions: v.number(),
160
168
  revenueCents: v.number(),
161
169
  dimensions: v.array(dimensionSlotValidator),