@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
@@ -1,7 +1,11 @@
1
1
  import { mutationGeneric, paginationOptsValidator, paginationResultValidator, queryGeneric, } from "convex/server";
2
2
  import { v } from "convex/values";
3
- import { eventPropertiesValidator, eventTypeValidator, funnelStepValidator, goalMatchTypeValidator, ingestContextValidator, ingestResultValidator, trackerEventValidator, } from "../component/validators.js";
4
- export { eventPropertiesValidator, eventTypeValidator, funnelStepValidator, goalMatchTypeValidator, ingestContextValidator, ingestResultValidator, trackerEventValidator, };
3
+ import { eventPropertiesValidator, eventTypeValidator, funnelStepValidator, goalMatchTypeValidator, ingestContextValidator, ingestResultValidator, trackerEventValidator, vitalMetricValidator, } from "../component/validators.js";
4
+ import { createFederatedAnalyticsConnectionAuthorizer, createFederatedAnalyticsManifest, FEDERATED_ANALYTICS_CONNECTOR_MANIFEST, federatedAnalyticsConnectionStatusValidator, federatedAnalyticsManifestValidator, federatedSiteSummaryValidator, } from "./federation.js";
5
+ export { eventPropertiesValidator, eventTypeValidator, funnelStepValidator, goalMatchTypeValidator, ingestContextValidator, ingestResultValidator, trackerEventValidator, vitalMetricValidator, };
6
+ export { VITAL_METRICS, VITAL_THRESHOLDS } from "../component/vitals.js";
7
+ export { normalizeRoute } from "../component/sanitize.js";
8
+ export { FEDERATED_ANALYTICS_CLAIMS, FEDERATED_ANALYTICS_CONNECTOR_MANIFEST, FEDERATED_ANALYTICS_ERROR_CODES, FEDERATED_ANALYTICS_READ_PERMISSION, FEDERATED_ANALYTICS_REQUIRED_CAPABILITIES, FEDERATED_ANALYTICS_REQUIRED_FUNCTIONS, } from "./federation.js";
5
9
  export class Rastro {
6
10
  component;
7
11
  constructor(component) {
@@ -25,6 +29,9 @@ export class Rastro {
25
29
  trackConversion(ctx, args) {
26
30
  return ctx.runMutation(this.component.ingest.trackConversion, args);
27
31
  }
32
+ linkVisitor(ctx, args) {
33
+ return ctx.runMutation(this.component.visitors.link, args);
34
+ }
28
35
  overview(ctx, args) {
29
36
  return ctx.runQuery(this.component.reports.overview, args);
30
37
  }
@@ -52,6 +59,12 @@ export class Rastro {
52
59
  affiliatesReport(ctx, args) {
53
60
  return ctx.runQuery(this.component.reports.affiliatesReport, args);
54
61
  }
62
+ vitalsReport(ctx, args) {
63
+ return ctx.runQuery(this.component.reports.vitalsReport, args);
64
+ }
65
+ siteMap(ctx, args) {
66
+ return ctx.runQuery(this.component.reports.siteMap, args);
67
+ }
55
68
  upsertGoal(ctx, args) {
56
69
  return ctx.runMutation(this.component.goals.upsert, args);
57
70
  }
@@ -77,11 +90,29 @@ export class Rastro {
77
90
  return ctx.runQuery(this.component.affiliates.list, { siteId });
78
91
  }
79
92
  removeAffiliate(ctx, siteId, affiliateId) {
80
- return ctx.runMutation(this.component.affiliates.remove, { siteId, affiliateId });
93
+ return ctx.runMutation(this.component.affiliates.remove, {
94
+ siteId,
95
+ affiliateId,
96
+ });
81
97
  }
82
98
  cleanup(ctx, args) {
83
99
  return ctx.runMutation(this.component.retention.cleanup, args);
84
100
  }
101
+ dataCoverage(ctx, args) {
102
+ return ctx.runQuery(this.component.reports.dataCoverage, args);
103
+ }
104
+ setRetentionPolicy(ctx, args) {
105
+ return ctx.runMutation(this.component.retention.setPolicy, args);
106
+ }
107
+ disableRetentionPolicy(ctx, siteId) {
108
+ return ctx.runMutation(this.component.retention.disablePolicy, { siteId });
109
+ }
110
+ retentionStatus(ctx, siteId) {
111
+ return ctx.runQuery(this.component.retention.status, { siteId });
112
+ }
113
+ runRetentionPolicy(ctx, args) {
114
+ return ctx.runMutation(this.component.retention.runPolicy, args);
115
+ }
85
116
  }
86
117
  const hostSiteValidator = v.object({
87
118
  _id: v.string(),
@@ -111,6 +142,7 @@ const hostSessionValidator = v.object({
111
142
  utmSource: v.optional(v.string()),
112
143
  utmMedium: v.optional(v.string()),
113
144
  utmCampaign: v.optional(v.string()),
145
+ visitorKey: v.optional(v.string()),
114
146
  country: v.optional(v.string()),
115
147
  city: v.optional(v.string()),
116
148
  latitude: v.optional(v.number()),
@@ -176,6 +208,8 @@ const hostLiveVisitorValidator = v.object({
176
208
  device: v.string(),
177
209
  pageviewCount: v.number(),
178
210
  eventCount: v.number(),
211
+ previousPath: v.optional(v.string()),
212
+ movedAt: v.optional(v.number()),
179
213
  });
180
214
  const hostConversionValidator = v.object({
181
215
  _id: v.string(),
@@ -212,6 +246,53 @@ const hostTopItemValidator = v.object({
212
246
  count: v.number(),
213
247
  revenueCents: v.number(),
214
248
  });
249
+ const hostCoverageValidator = v.object({
250
+ state: v.union(v.literal("complete"), v.literal("partial"), v.literal("retained"), v.literal("unavailable")),
251
+ generation: v.union(v.number(), v.null()),
252
+ availableFrom: v.union(v.number(), v.null()),
253
+ availableThrough: v.union(v.number(), v.null()),
254
+ retainedBefore: v.union(v.number(), v.null()),
255
+ });
256
+ const hostRangeSemanticsValidator = v.object({
257
+ from: v.number(),
258
+ to: v.number(),
259
+ boundary: v.literal("inclusive"),
260
+ timezone: v.literal("UTC"),
261
+ });
262
+ const hostReportMetadataValidator = v.object({
263
+ basis: v.union(v.literal("dailyRollup"), v.literal("boundedLegacy")),
264
+ coverage: hostCoverageValidator,
265
+ range: hostRangeSemanticsValidator,
266
+ });
267
+ const hostDataCoverageValidator = v.object({
268
+ siteId: v.string(),
269
+ from: v.number(),
270
+ to: v.number(),
271
+ datasets: v.array(v.object({
272
+ dataset: v.union(v.literal("overviewHour"), v.literal("overviewDay"), v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goals"), v.literal("funnels"), v.literal("affiliates"), v.literal("vitals"), v.literal("siteMap")),
273
+ coverage: hostCoverageValidator,
274
+ })),
275
+ });
276
+ const retentionKindValidator = v.union(v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goalCompletions"), v.literal("aggregateHour"), v.literal("aggregateDay"), v.literal("funnelProgress"), v.literal("funnelSteps"), v.literal("affiliateStats"), v.literal("goalRollups"), v.literal("funnelRollups"), v.literal("affiliateRollups"), v.literal("vitalRollups"), v.literal("siteMapRollups"), v.literal("attributions"), v.literal("liveSessions"), v.literal("ingestWindows"), v.literal("siteIngestWindows"));
277
+ const hostRetentionPolicyValidator = v.object({
278
+ eventsDays: v.number(),
279
+ sessionsDays: v.number(),
280
+ conversionsDays: v.number(),
281
+ aggregatesDays: v.number(),
282
+ reportsDays: v.number(),
283
+ });
284
+ const hostRetentionStatusValidator = v.object({
285
+ policy: v.union(hostRetentionPolicyValidator.extend({ updatedAt: v.number() }), v.null()),
286
+ jobs: v.array(v.object({
287
+ kind: v.string(),
288
+ before: v.number(),
289
+ status: v.union(v.literal("pending"), v.literal("running"), v.literal("complete")),
290
+ deleted: v.number(),
291
+ scheduledAt: v.union(v.number(), v.null()),
292
+ updatedAt: v.number(),
293
+ })),
294
+ backlog: v.number(),
295
+ });
215
296
  const hostOverviewValidator = v.object({
216
297
  from: v.number(),
217
298
  to: v.number(),
@@ -220,12 +301,40 @@ const hostOverviewValidator = v.object({
220
301
  totals: hostMetricTotalsValidator,
221
302
  timeSeries: v.array(hostMetricTotalsValidator.extend({ timestamp: v.number() })),
222
303
  topSources: v.array(hostTopItemValidator),
304
+ topCampaigns: v.array(hostTopItemValidator),
223
305
  topPages: v.array(hostTopItemValidator),
224
306
  topCountries: v.array(hostTopItemValidator),
225
307
  topDevices: v.array(hostTopItemValidator),
226
308
  topBrowsers: v.array(hostTopItemValidator),
227
309
  topEvents: v.array(hostTopItemValidator),
228
310
  topGoals: v.array(hostTopItemValidator),
311
+ metadata: v.object({
312
+ visitors: v.object({
313
+ basis: v.union(v.literal("estimatedUnique"), v.literal("bucketOccurrences")),
314
+ exact: v.boolean(),
315
+ legacyIncomplete: v.boolean(),
316
+ sketchVersion: v.union(v.number(), v.null()),
317
+ precision: v.union(v.number(), v.null()),
318
+ }),
319
+ breakdowns: v.object({
320
+ topSources: v.literal("eventVolume"),
321
+ topCampaigns: v.literal("eventVolume"),
322
+ topPages: v.literal("eventVolume"),
323
+ topCountries: v.literal("eventVolume"),
324
+ topDevices: v.literal("eventVolume"),
325
+ topBrowsers: v.literal("eventVolume"),
326
+ topEvents: v.literal("eventVolume"),
327
+ topGoals: v.literal("goalCompletionVolume"),
328
+ }),
329
+ coverage: hostCoverageValidator,
330
+ range: hostRangeSemanticsValidator,
331
+ features: v.object({
332
+ sessionReplay: v.literal("unsupported"),
333
+ errorInsights: v.literal("unsupported"),
334
+ outboundLinks: v.literal("eventVolume"),
335
+ botDetection: v.literal("userAgent"),
336
+ }),
337
+ }),
229
338
  });
230
339
  const hostGoalValidator = v.object({
231
340
  _id: v.string(),
@@ -265,12 +374,22 @@ const hostAffiliateValidator = v.object({
265
374
  updatedAt: v.number(),
266
375
  });
267
376
  const hostGoalsReportValidator = v.object({
268
- goals: v.array(hostGoalValidator.extend({ completions: v.number(), valueCents: v.number() })),
377
+ goals: v.array(hostGoalValidator.extend({
378
+ completions: v.number(),
379
+ valueCents: v.number(),
380
+ })),
269
381
  truncated: v.boolean(),
382
+ metadata: hostReportMetadataValidator,
270
383
  });
271
384
  const hostFunnelsReportValidator = v.array(hostFunnelValidator.extend({
272
- stepsReport: v.array(v.object({ step: v.number(), name: v.string(), entrants: v.number(), completions: v.number() })),
385
+ stepsReport: v.array(v.object({
386
+ step: v.number(),
387
+ name: v.string(),
388
+ entrants: v.number(),
389
+ completions: v.number(),
390
+ })),
273
391
  truncated: v.boolean(),
392
+ metadata: hostReportMetadataValidator,
274
393
  }));
275
394
  const hostAffiliatesReportValidator = v.object({
276
395
  affiliates: v.array(hostAffiliateValidator.extend({
@@ -281,6 +400,65 @@ const hostAffiliatesReportValidator = v.object({
281
400
  commissionCents: v.number(),
282
401
  })),
283
402
  truncated: v.boolean(),
403
+ metadata: hostReportMetadataValidator.extend({
404
+ uniqueVisitors: v.object({
405
+ basis: v.union(v.literal("estimatedUnique"), v.literal("exactLegacy")),
406
+ exact: v.boolean(),
407
+ }),
408
+ }),
409
+ });
410
+ const hostVitalStatsValidator = v.object({
411
+ count: v.number(),
412
+ p75: v.number(),
413
+ average: v.number(),
414
+ good: v.number(),
415
+ needsImprovement: v.number(),
416
+ poor: v.number(),
417
+ });
418
+ const hostVitalsReportValidator = v.object({
419
+ metrics: v.array(hostVitalStatsValidator.extend({
420
+ metric: vitalMetricValidator,
421
+ devices: v.array(hostVitalStatsValidator.extend({ device: v.string() })),
422
+ pages: v.array(hostVitalStatsValidator.extend({ page: v.string() })),
423
+ timeSeries: v.array(v.object({
424
+ timestamp: v.number(),
425
+ count: v.number(),
426
+ p75: v.number(),
427
+ })),
428
+ })),
429
+ truncated: v.boolean(),
430
+ metadata: hostReportMetadataValidator,
431
+ });
432
+ const hostSiteMapValidator = v.object({
433
+ from: v.number(),
434
+ to: v.number(),
435
+ window: v.union(v.object({ from: v.number(), to: v.number() }), v.null()),
436
+ routes: v.array(v.object({
437
+ route: v.string(),
438
+ pageviews: v.number(),
439
+ visitors: v.number(),
440
+ entries: v.number(),
441
+ exits: v.number(),
442
+ bounces: v.number(),
443
+ transitionsIn: v.number(),
444
+ transitionsOut: v.number(),
445
+ dwellMs: v.number(),
446
+ dwellSamples: v.number(),
447
+ daily: v.array(v.object({ bucketStart: v.number(), pageviews: v.number() })),
448
+ })),
449
+ transitions: v.array(v.object({ from: v.string(), to: v.string(), count: v.number() })),
450
+ truncated: v.object({ routes: v.boolean(), transitions: v.boolean() }),
451
+ metadata: hostReportMetadataValidator.extend({
452
+ visitors: v.object({
453
+ basis: v.literal("estimatedUnique"),
454
+ exact: v.boolean(),
455
+ }),
456
+ limits: v.object({
457
+ routesPerDay: v.number(),
458
+ transitionSlots: v.number(),
459
+ rows: v.number(),
460
+ }),
461
+ }),
284
462
  });
285
463
  export function exposeAnalyticsApi(component, options) {
286
464
  const rastro = new Rastro(component);
@@ -297,7 +475,11 @@ export function exposeAnalyticsApi(component, options) {
297
475
  },
298
476
  returns: v.string(),
299
477
  handler: async (ctx, args) => {
300
- await options.authorize(ctx, { operation: "site.create", siteIds: [], ownerId: args.ownerId });
478
+ await options.authorize(ctx, {
479
+ operation: "site.create",
480
+ siteIds: [],
481
+ ownerId: args.ownerId,
482
+ });
301
483
  return await rastro.createSite(ctx, args);
302
484
  },
303
485
  }),
@@ -313,7 +495,10 @@ export function exposeAnalyticsApi(component, options) {
313
495
  },
314
496
  returns: v.null(),
315
497
  handler: async (ctx, args) => {
316
- await options.authorize(ctx, { operation: "site.update", siteIds: [args.siteId] });
498
+ await options.authorize(ctx, {
499
+ operation: "site.update",
500
+ siteIds: [args.siteId],
501
+ });
317
502
  return await rastro.updateSite(ctx, args);
318
503
  },
319
504
  }),
@@ -321,7 +506,10 @@ export function exposeAnalyticsApi(component, options) {
321
506
  args: { siteId: v.string() },
322
507
  returns: v.union(hostSiteValidator, v.null()),
323
508
  handler: async (ctx, args) => {
324
- await options.authorize(ctx, { operation: "site.get", siteIds: [args.siteId] });
509
+ await options.authorize(ctx, {
510
+ operation: "site.get",
511
+ siteIds: [args.siteId],
512
+ });
325
513
  return await rastro.getSite(ctx, args.siteId);
326
514
  },
327
515
  }),
@@ -329,7 +517,11 @@ export function exposeAnalyticsApi(component, options) {
329
517
  args: { ownerId: v.string(), limit: v.optional(v.number()) },
330
518
  returns: v.array(hostSiteValidator),
331
519
  handler: async (ctx, args) => {
332
- await options.authorize(ctx, { operation: "site.list", siteIds: [], ownerId: args.ownerId });
520
+ await options.authorize(ctx, {
521
+ operation: "site.list",
522
+ siteIds: [],
523
+ ownerId: args.ownerId,
524
+ });
333
525
  return await rastro.listSites(ctx, args);
334
526
  },
335
527
  }),
@@ -342,7 +534,10 @@ export function exposeAnalyticsApi(component, options) {
342
534
  },
343
535
  returns: ingestResultValidator,
344
536
  handler: async (ctx, args) => {
345
- await options.authorize(ctx, { operation: "ingest", siteIds: [args.siteId] });
537
+ await options.authorize(ctx, {
538
+ operation: "ingest",
539
+ siteIds: [args.siteId],
540
+ });
346
541
  return await rastro.ingest(ctx, args);
347
542
  },
348
543
  }),
@@ -361,10 +556,28 @@ export function exposeAnalyticsApi(component, options) {
361
556
  },
362
557
  returns: ingestResultValidator,
363
558
  handler: async (ctx, args) => {
364
- await options.authorize(ctx, { operation: "conversion.track", siteIds: [args.siteId] });
559
+ await options.authorize(ctx, {
560
+ operation: "conversion.track",
561
+ siteIds: [args.siteId],
562
+ });
365
563
  return await rastro.trackConversion(ctx, args);
366
564
  },
367
565
  }),
566
+ linkVisitor: mutationGeneric({
567
+ args: {
568
+ siteId: v.string(),
569
+ visitorId: v.string(),
570
+ previousVisitorId: v.string(),
571
+ },
572
+ returns: v.object({ linked: v.boolean(), aliasCount: v.number() }),
573
+ handler: async (ctx, args) => {
574
+ await options.authorize(ctx, {
575
+ operation: "visitor.link",
576
+ siteIds: [args.siteId],
577
+ });
578
+ return await rastro.linkVisitor(ctx, args);
579
+ },
580
+ }),
368
581
  overview: queryGeneric({
369
582
  args: {
370
583
  siteIds: v.array(v.string()),
@@ -374,7 +587,10 @@ export function exposeAnalyticsApi(component, options) {
374
587
  },
375
588
  returns: hostOverviewValidator,
376
589
  handler: async (ctx, args) => {
377
- await options.authorize(ctx, { operation: "report.overview", siteIds: args.siteIds });
590
+ await options.authorize(ctx, {
591
+ operation: "report.overview",
592
+ siteIds: args.siteIds,
593
+ });
378
594
  return await rastro.overview(ctx, args);
379
595
  },
380
596
  }),
@@ -386,7 +602,10 @@ export function exposeAnalyticsApi(component, options) {
386
602
  },
387
603
  returns: v.array(hostLiveVisitorValidator),
388
604
  handler: async (ctx, args) => {
389
- await options.authorize(ctx, { operation: "report.live", siteIds: args.siteIds });
605
+ await options.authorize(ctx, {
606
+ operation: "report.live",
607
+ siteIds: args.siteIds,
608
+ });
390
609
  return await rastro.liveVisitors(ctx, args);
391
610
  },
392
611
  }),
@@ -399,7 +618,10 @@ export function exposeAnalyticsApi(component, options) {
399
618
  },
400
619
  returns: paginationResultValidator(hostSessionValidator),
401
620
  handler: async (ctx, args) => {
402
- await options.authorize(ctx, { operation: "report.sessions", siteIds: [args.siteId] });
621
+ await options.authorize(ctx, {
622
+ operation: "report.sessions",
623
+ siteIds: [args.siteId],
624
+ });
403
625
  return await rastro.listSessions(ctx, args);
404
626
  },
405
627
  }),
@@ -413,7 +635,10 @@ export function exposeAnalyticsApi(component, options) {
413
635
  },
414
636
  returns: paginationResultValidator(hostEventValidator),
415
637
  handler: async (ctx, args) => {
416
- await options.authorize(ctx, { operation: "report.sessionJourney", siteIds: [args.siteId] });
638
+ await options.authorize(ctx, {
639
+ operation: "report.sessionJourney",
640
+ siteIds: [args.siteId],
641
+ });
417
642
  return await rastro.sessionJourney(ctx, args);
418
643
  },
419
644
  }),
@@ -426,7 +651,10 @@ export function exposeAnalyticsApi(component, options) {
426
651
  },
427
652
  returns: paginationResultValidator(hostConversionValidator),
428
653
  handler: async (ctx, args) => {
429
- await options.authorize(ctx, { operation: "report.conversions", siteIds: [args.siteId] });
654
+ await options.authorize(ctx, {
655
+ operation: "report.conversions",
656
+ siteIds: [args.siteId],
657
+ });
430
658
  return await rastro.listConversions(ctx, args);
431
659
  },
432
660
  }),
@@ -440,7 +668,10 @@ export function exposeAnalyticsApi(component, options) {
440
668
  },
441
669
  returns: v.array(hostEventValidator),
442
670
  handler: async (ctx, args) => {
443
- await options.authorize(ctx, { operation: "report.visitorJourney", siteIds: args.siteIds });
671
+ await options.authorize(ctx, {
672
+ operation: "report.visitorJourney",
673
+ siteIds: args.siteIds,
674
+ });
444
675
  return await rastro.visitorJourney(ctx, args);
445
676
  },
446
677
  }),
@@ -448,7 +679,10 @@ export function exposeAnalyticsApi(component, options) {
448
679
  args: { siteId: v.string(), from: v.number(), to: v.number() },
449
680
  returns: hostGoalsReportValidator,
450
681
  handler: async (ctx, args) => {
451
- await options.authorize(ctx, { operation: "report.goals", siteIds: [args.siteId] });
682
+ await options.authorize(ctx, {
683
+ operation: "report.goals",
684
+ siteIds: [args.siteId],
685
+ });
452
686
  return await rastro.goalsReport(ctx, args);
453
687
  },
454
688
  }),
@@ -456,7 +690,10 @@ export function exposeAnalyticsApi(component, options) {
456
690
  args: { siteId: v.string(), from: v.number(), to: v.number() },
457
691
  returns: hostFunnelsReportValidator,
458
692
  handler: async (ctx, args) => {
459
- await options.authorize(ctx, { operation: "report.funnels", siteIds: [args.siteId] });
693
+ await options.authorize(ctx, {
694
+ operation: "report.funnels",
695
+ siteIds: [args.siteId],
696
+ });
460
697
  return await rastro.funnelsReport(ctx, args);
461
698
  },
462
699
  }),
@@ -464,10 +701,102 @@ export function exposeAnalyticsApi(component, options) {
464
701
  args: { siteId: v.string(), from: v.number(), to: v.number() },
465
702
  returns: hostAffiliatesReportValidator,
466
703
  handler: async (ctx, args) => {
467
- await options.authorize(ctx, { operation: "report.affiliates", siteIds: [args.siteId] });
704
+ await options.authorize(ctx, {
705
+ operation: "report.affiliates",
706
+ siteIds: [args.siteId],
707
+ });
468
708
  return await rastro.affiliatesReport(ctx, args);
469
709
  },
470
710
  }),
711
+ vitalsReport: queryGeneric({
712
+ args: { siteId: v.string(), from: v.number(), to: v.number() },
713
+ returns: hostVitalsReportValidator,
714
+ handler: async (ctx, args) => {
715
+ await options.authorize(ctx, {
716
+ operation: "report.vitals",
717
+ siteIds: [args.siteId],
718
+ });
719
+ return await rastro.vitalsReport(ctx, args);
720
+ },
721
+ }),
722
+ siteMap: queryGeneric({
723
+ args: {
724
+ siteIds: v.array(v.string()),
725
+ from: v.number(),
726
+ to: v.number(),
727
+ },
728
+ returns: hostSiteMapValidator,
729
+ handler: async (ctx, args) => {
730
+ await options.authorize(ctx, {
731
+ operation: "report.siteMap",
732
+ siteIds: args.siteIds,
733
+ });
734
+ return await rastro.siteMap(ctx, args);
735
+ },
736
+ }),
737
+ dataCoverage: queryGeneric({
738
+ args: { siteId: v.string(), from: v.number(), to: v.number() },
739
+ returns: hostDataCoverageValidator,
740
+ handler: async (ctx, args) => {
741
+ await options.authorize(ctx, {
742
+ operation: "coverage.read",
743
+ siteIds: [args.siteId],
744
+ });
745
+ return await rastro.dataCoverage(ctx, args);
746
+ },
747
+ }),
748
+ retentionStatus: queryGeneric({
749
+ args: { siteId: v.string() },
750
+ returns: hostRetentionStatusValidator,
751
+ handler: async (ctx, args) => {
752
+ await options.authorize(ctx, {
753
+ operation: "retention.status",
754
+ siteIds: [args.siteId],
755
+ });
756
+ return await rastro.retentionStatus(ctx, args.siteId);
757
+ },
758
+ }),
759
+ setRetentionPolicy: mutationGeneric({
760
+ args: { siteId: v.string(), ...hostRetentionPolicyValidator.fields },
761
+ returns: v.null(),
762
+ handler: async (ctx, args) => {
763
+ await options.authorize(ctx, {
764
+ operation: "retention.policy",
765
+ siteIds: [args.siteId],
766
+ });
767
+ return await rastro.setRetentionPolicy(ctx, args);
768
+ },
769
+ }),
770
+ disableRetentionPolicy: mutationGeneric({
771
+ args: { siteId: v.string() },
772
+ returns: v.null(),
773
+ handler: async (ctx, args) => {
774
+ await options.authorize(ctx, {
775
+ operation: "retention.policy",
776
+ siteIds: [args.siteId],
777
+ });
778
+ return await rastro.disableRetentionPolicy(ctx, args.siteId);
779
+ },
780
+ }),
781
+ runRetentionPolicy: mutationGeneric({
782
+ args: {
783
+ siteId: v.string(),
784
+ kind: retentionKindValidator,
785
+ batchSize: v.optional(v.number()),
786
+ },
787
+ returns: v.object({
788
+ deleted: v.number(),
789
+ hasMore: v.boolean(),
790
+ before: v.number(),
791
+ }),
792
+ handler: async (ctx, args) => {
793
+ await options.authorize(ctx, {
794
+ operation: "retention.policy",
795
+ siteIds: [args.siteId],
796
+ });
797
+ return await rastro.runRetentionPolicy(ctx, args);
798
+ },
799
+ }),
471
800
  upsertGoal: mutationGeneric({
472
801
  args: {
473
802
  siteId: v.string(),
@@ -481,7 +810,10 @@ export function exposeAnalyticsApi(component, options) {
481
810
  },
482
811
  returns: v.string(),
483
812
  handler: async (ctx, args) => {
484
- await options.authorize(ctx, { operation: "goal.upsert", siteIds: [args.siteId] });
813
+ await options.authorize(ctx, {
814
+ operation: "goal.upsert",
815
+ siteIds: [args.siteId],
816
+ });
485
817
  return await rastro.upsertGoal(ctx, args);
486
818
  },
487
819
  }),
@@ -489,7 +821,10 @@ export function exposeAnalyticsApi(component, options) {
489
821
  args: { siteId: v.string() },
490
822
  returns: v.array(hostGoalValidator),
491
823
  handler: async (ctx, args) => {
492
- await options.authorize(ctx, { operation: "goal.list", siteIds: [args.siteId] });
824
+ await options.authorize(ctx, {
825
+ operation: "goal.list",
826
+ siteIds: [args.siteId],
827
+ });
493
828
  return await rastro.listGoals(ctx, args.siteId);
494
829
  },
495
830
  }),
@@ -497,7 +832,10 @@ export function exposeAnalyticsApi(component, options) {
497
832
  args: { siteId: v.string(), goalId: v.string() },
498
833
  returns: v.null(),
499
834
  handler: async (ctx, args) => {
500
- await options.authorize(ctx, { operation: "goal.remove", siteIds: [args.siteId] });
835
+ await options.authorize(ctx, {
836
+ operation: "goal.remove",
837
+ siteIds: [args.siteId],
838
+ });
501
839
  return await rastro.removeGoal(ctx, args.siteId, args.goalId);
502
840
  },
503
841
  }),
@@ -513,7 +851,10 @@ export function exposeAnalyticsApi(component, options) {
513
851
  },
514
852
  returns: v.string(),
515
853
  handler: async (ctx, args) => {
516
- await options.authorize(ctx, { operation: "funnel.upsert", siteIds: [args.siteId] });
854
+ await options.authorize(ctx, {
855
+ operation: "funnel.upsert",
856
+ siteIds: [args.siteId],
857
+ });
517
858
  return await rastro.upsertFunnel(ctx, args);
518
859
  },
519
860
  }),
@@ -521,7 +862,10 @@ export function exposeAnalyticsApi(component, options) {
521
862
  args: { siteId: v.string() },
522
863
  returns: v.array(hostFunnelValidator),
523
864
  handler: async (ctx, args) => {
524
- await options.authorize(ctx, { operation: "funnel.list", siteIds: [args.siteId] });
865
+ await options.authorize(ctx, {
866
+ operation: "funnel.list",
867
+ siteIds: [args.siteId],
868
+ });
525
869
  return await rastro.listFunnels(ctx, args.siteId);
526
870
  },
527
871
  }),
@@ -529,7 +873,10 @@ export function exposeAnalyticsApi(component, options) {
529
873
  args: { siteId: v.string(), funnelId: v.string() },
530
874
  returns: v.null(),
531
875
  handler: async (ctx, args) => {
532
- await options.authorize(ctx, { operation: "funnel.remove", siteIds: [args.siteId] });
876
+ await options.authorize(ctx, {
877
+ operation: "funnel.remove",
878
+ siteIds: [args.siteId],
879
+ });
533
880
  return await rastro.removeFunnel(ctx, args.siteId, args.funnelId);
534
881
  },
535
882
  }),
@@ -545,7 +892,10 @@ export function exposeAnalyticsApi(component, options) {
545
892
  },
546
893
  returns: v.string(),
547
894
  handler: async (ctx, args) => {
548
- await options.authorize(ctx, { operation: "affiliate.upsert", siteIds: [args.siteId] });
895
+ await options.authorize(ctx, {
896
+ operation: "affiliate.upsert",
897
+ siteIds: [args.siteId],
898
+ });
549
899
  return await rastro.upsertAffiliate(ctx, args);
550
900
  },
551
901
  }),
@@ -553,7 +903,10 @@ export function exposeAnalyticsApi(component, options) {
553
903
  args: { siteId: v.string() },
554
904
  returns: v.array(hostAffiliateValidator),
555
905
  handler: async (ctx, args) => {
556
- await options.authorize(ctx, { operation: "affiliate.list", siteIds: [args.siteId] });
906
+ await options.authorize(ctx, {
907
+ operation: "affiliate.list",
908
+ siteIds: [args.siteId],
909
+ });
557
910
  return await rastro.listAffiliates(ctx, args.siteId);
558
911
  },
559
912
  }),
@@ -561,30 +914,106 @@ export function exposeAnalyticsApi(component, options) {
561
914
  args: { siteId: v.string(), affiliateId: v.string() },
562
915
  returns: v.null(),
563
916
  handler: async (ctx, args) => {
564
- await options.authorize(ctx, { operation: "affiliate.remove", siteIds: [args.siteId] });
917
+ await options.authorize(ctx, {
918
+ operation: "affiliate.remove",
919
+ siteIds: [args.siteId],
920
+ });
565
921
  return await rastro.removeAffiliate(ctx, args.siteId, args.affiliateId);
566
922
  },
567
923
  }),
568
924
  cleanupAnalytics: mutationGeneric({
569
925
  args: {
570
926
  siteId: v.string(),
571
- kind: v.union(v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goalCompletions"), v.literal("aggregateHour"), v.literal("aggregateDay"), v.literal("funnelProgress"), v.literal("funnelSteps"), v.literal("affiliateStats"), v.literal("attributions"), v.literal("liveSessions"), v.literal("ingestWindows"), v.literal("siteIngestWindows")),
927
+ kind: v.union(v.literal("events"), v.literal("sessions"), v.literal("conversions"), v.literal("goalCompletions"), v.literal("aggregateHour"), v.literal("aggregateDay"), v.literal("funnelProgress"), v.literal("funnelSteps"), v.literal("affiliateStats"), v.literal("goalRollups"), v.literal("funnelRollups"), v.literal("affiliateRollups"), v.literal("vitalRollups"), v.literal("attributions"), v.literal("liveSessions"), v.literal("ingestWindows"), v.literal("siteIngestWindows")),
572
928
  before: v.number(),
573
929
  batchSize: v.optional(v.number()),
574
930
  },
575
- returns: v.object({ deleted: v.number(), hasMore: v.boolean() }),
931
+ returns: v.object({
932
+ deleted: v.number(),
933
+ hasMore: v.boolean(),
934
+ before: v.number(),
935
+ }),
576
936
  handler: async (ctx, args) => {
577
- await options.authorize(ctx, { operation: "retention.cleanup", siteIds: [args.siteId] });
937
+ await options.authorize(ctx, {
938
+ operation: "retention.cleanup",
939
+ siteIds: [args.siteId],
940
+ });
578
941
  return await rastro.cleanup(ctx, args);
579
942
  },
580
943
  }),
581
944
  };
582
945
  }
946
+ /** Exposes only the reactive read surface used by a federated Rastro dashboard. */
947
+ export function exposeFederatedAnalyticsApi(component, options) {
948
+ const authorizeConnection = createFederatedAnalyticsConnectionAuthorizer(options);
949
+ const api = exposeAnalyticsApi(component, {
950
+ authorize: async (ctx, request) => {
951
+ await authorizeConnection(ctx, request.siteIds);
952
+ },
953
+ });
954
+ const rastro = new Rastro(component);
955
+ return {
956
+ manifest: queryGeneric({
957
+ args: {},
958
+ returns: federatedAnalyticsManifestValidator,
959
+ handler: () => createFederatedAnalyticsManifest(),
960
+ }),
961
+ connectionStatus: queryGeneric({
962
+ args: {},
963
+ returns: federatedAnalyticsConnectionStatusValidator,
964
+ handler: async (ctx) => {
965
+ const connection = await authorizeConnection(ctx);
966
+ return {
967
+ status: "connected",
968
+ protocolVersion: FEDERATED_ANALYTICS_CONNECTOR_MANIFEST.protocolVersion,
969
+ capabilities: [
970
+ ...FEDERATED_ANALYTICS_CONNECTOR_MANIFEST.capabilities,
971
+ ],
972
+ siteCount: connection.siteIds.length,
973
+ };
974
+ },
975
+ }),
976
+ listSites: queryGeneric({
977
+ args: {},
978
+ returns: v.array(federatedSiteSummaryValidator),
979
+ handler: async (ctx) => {
980
+ const connection = await authorizeConnection(ctx);
981
+ const summaries = [];
982
+ for (const siteId of connection.siteIds) {
983
+ const site = await rastro.getSite(ctx, siteId);
984
+ if (!site)
985
+ continue;
986
+ summaries.push({
987
+ siteId: site._id,
988
+ name: site.name,
989
+ currency: site.currency ?? "USD",
990
+ timezone: site.timezone ?? null,
991
+ cookieless: site.cookieless,
992
+ });
993
+ }
994
+ return summaries;
995
+ },
996
+ }),
997
+ overview: api.overview,
998
+ liveVisitors: api.liveVisitors,
999
+ listSessions: api.listSessions,
1000
+ sessionJourney: api.sessionJourney,
1001
+ listConversions: api.listConversions,
1002
+ visitorJourney: api.visitorJourney,
1003
+ goalsReport: api.goalsReport,
1004
+ funnelsReport: api.funnelsReport,
1005
+ affiliatesReport: api.affiliatesReport,
1006
+ vitalsReport: api.vitalsReport,
1007
+ siteMap: api.siteMap,
1008
+ dataCoverage: api.dataCoverage,
1009
+ };
1010
+ }
583
1011
  export function componentHttpPaths(prefix = "/rastro/") {
584
1012
  const normalized = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
585
1013
  return {
586
1014
  events: `${normalized}/events`,
587
1015
  tracker: `${normalized}/tracker.js`,
1016
+ vitals: `${normalized}/vitals.js`,
588
1017
  health: `${normalized}/health`,
589
1018
  };
590
1019
  }