@nuxtseo/protocol 0.1.0

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 (44) hide show
  1. package/dist/analytics-codecs/analytics-row.d.ts +265 -0
  2. package/dist/analytics-codecs/analytics-row.js +175 -0
  3. package/dist/analytics-codecs/analytics-web-vitals.d.ts +57 -0
  4. package/dist/analytics-codecs/analytics-web-vitals.js +76 -0
  5. package/dist/analytics-codecs/capabilities.d.ts +35 -0
  6. package/dist/analytics-codecs/capabilities.js +76 -0
  7. package/dist/analytics-codecs/index.d.ts +3 -0
  8. package/dist/analytics-codecs/index.js +3 -0
  9. package/dist/cli-pairing.d.ts +67 -0
  10. package/dist/cli-pairing.js +52 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/v1/account.d.ts +715 -0
  14. package/dist/v1/account.js +123 -0
  15. package/dist/v1/actions.d.ts +2591 -0
  16. package/dist/v1/actions.js +460 -0
  17. package/dist/v1/analytics.d.ts +6145 -0
  18. package/dist/v1/analytics.js +296 -0
  19. package/dist/v1/audit.d.ts +4053 -0
  20. package/dist/v1/audit.js +609 -0
  21. package/dist/v1/backlinks.d.ts +1002 -0
  22. package/dist/v1/backlinks.js +239 -0
  23. package/dist/v1/content.d.ts +2745 -0
  24. package/dist/v1/content.js +326 -0
  25. package/dist/v1/core.d.ts +301 -0
  26. package/dist/v1/core.js +333 -0
  27. package/dist/v1/documents.d.ts +4 -0
  28. package/dist/v1/documents.js +216 -0
  29. package/dist/v1/gsc.d.ts +5172 -0
  30. package/dist/v1/gsc.js +583 -0
  31. package/dist/v1/index.d.ts +49 -0
  32. package/dist/v1/index.js +52 -0
  33. package/dist/v1/pages.d.ts +1404 -0
  34. package/dist/v1/pages.js +287 -0
  35. package/dist/v1/performance.d.ts +1379 -0
  36. package/dist/v1/performance.js +198 -0
  37. package/dist/v1/research.d.ts +1173 -0
  38. package/dist/v1/research.js +174 -0
  39. package/dist/v1/sitemaps.d.ts +1506 -0
  40. package/dist/v1/sitemaps.js +199 -0
  41. package/dist/v1/sites.d.ts +591 -0
  42. package/dist/v1/sites.js +101 -0
  43. package/generated/openapi.v1.json +1 -0
  44. package/package.json +131 -0
@@ -0,0 +1,265 @@
1
+ import { z } from 'zod';
2
+ export declare const analyticsProviderSchema: z.ZodEnum<{
3
+ cloudflare: "cloudflare";
4
+ ga4: "ga4";
5
+ plausible: "plausible";
6
+ posthog: "posthog";
7
+ umami: "umami";
8
+ }>;
9
+ export type AnalyticsProvider = z.infer<typeof analyticsProviderSchema>;
10
+ /**
11
+ * Dimensions every provider can be asked to group by. `source`/`medium`/
12
+ * `campaign` are the UTM-style traffic-attribution triple — a provider with no
13
+ * analog for one of them (Cloudflare RUM has only a referrer host) resolves it
14
+ * to an empty string rather than erroring, mirroring how metrics resolve to 0.
15
+ * Provider-native dimensions with no cross-provider analog go through the
16
+ * per-provider escape hatch on the query, not this enum.
17
+ */
18
+ export declare const analyticsDimensionSchema: z.ZodEnum<{
19
+ browser: "browser";
20
+ campaign: "campaign";
21
+ city: "city";
22
+ country: "country";
23
+ date: "date";
24
+ deviceCategory: "deviceCategory";
25
+ hostname: "hostname";
26
+ medium: "medium";
27
+ operatingSystem: "operatingSystem";
28
+ page: "page";
29
+ region: "region";
30
+ source: "source";
31
+ }>;
32
+ export type AnalyticsDimension = z.infer<typeof analyticsDimensionSchema>;
33
+ /**
34
+ * Metrics every provider maps onto. Cloudflare Web Analytics has no concept of
35
+ * conversions/keyEvents; those resolve to 0 rather than erroring.
36
+ */
37
+ export declare const analyticsMetricSchema: z.ZodEnum<{
38
+ avgSessionDuration: "avgSessionDuration";
39
+ bounceRate: "bounceRate";
40
+ conversions: "conversions";
41
+ engagementRate: "engagementRate";
42
+ keyEvents: "keyEvents";
43
+ pageviews: "pageviews";
44
+ sessions: "sessions";
45
+ users: "users";
46
+ }>;
47
+ export type AnalyticsMetric = z.infer<typeof analyticsMetricSchema>;
48
+ /**
49
+ * Cloudflare-only RUM dimensions with no cross-provider analog. Kept out of the
50
+ * generic `analyticsDimensionSchema` (they would be dead weight on GA4); reached
51
+ * through the opt-in `cloudflare` escape hatch on the query and the row. The enum
52
+ * values are the raw Cloudflare RUM field names.
53
+ */
54
+ export declare const cloudflareDimensionSchema: z.ZodEnum<{
55
+ bot: "bot";
56
+ deliveryType: "deliveryType";
57
+ navigationType: "navigationType";
58
+ }>;
59
+ export type CloudflareDimension = z.infer<typeof cloudflareDimensionSchema>;
60
+ /** Query-side hatch — opt a provider into raw native dimensions/metrics. */
61
+ export declare const cloudflareQueryHatchSchema: z.ZodObject<{
62
+ dimensions: z.ZodArray<z.ZodEnum<{
63
+ bot: "bot";
64
+ deliveryType: "deliveryType";
65
+ navigationType: "navigationType";
66
+ }>>;
67
+ }, z.core.$strip>;
68
+ export type CloudflareQueryHatch = z.infer<typeof cloudflareQueryHatchSchema>;
69
+ export declare const ga4QueryHatchSchema: z.ZodObject<{
70
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
71
+ metrics: z.ZodOptional<z.ZodArray<z.ZodString>>;
72
+ }, z.core.$strip>;
73
+ export type Ga4QueryHatch = z.infer<typeof ga4QueryHatchSchema>;
74
+ export declare const posthogQueryHatchSchema: z.ZodObject<{
75
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
76
+ }, z.core.$strip>;
77
+ export type PosthogQueryHatch = z.infer<typeof posthogQueryHatchSchema>;
78
+ /** Row-side hatch — raw native values grouped onto one result row. */
79
+ export declare const cloudflareRowHatchSchema: z.ZodRecord<z.ZodEnum<{
80
+ bot: "bot";
81
+ deliveryType: "deliveryType";
82
+ navigationType: "navigationType";
83
+ }> & z.core.$partial, z.ZodString>;
84
+ export type CloudflareRowHatch = z.infer<typeof cloudflareRowHatchSchema>;
85
+ export declare const ga4RowHatchSchema: z.ZodObject<{
86
+ dimensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
87
+ metrics: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
88
+ }, z.core.$strip>;
89
+ export type Ga4RowHatch = z.infer<typeof ga4RowHatchSchema>;
90
+ export declare const posthogRowHatchSchema: z.ZodRecord<z.ZodString, z.ZodString>;
91
+ export type PosthogRowHatch = z.infer<typeof posthogRowHatchSchema>;
92
+ /**
93
+ * One grouped result: dimension values keyed by name, metric values keyed by
94
+ * name. Partial records — a row only carries what its query asked for.
95
+ */
96
+ export declare const analyticsRowSchema: z.ZodObject<{
97
+ dimensions: z.ZodRecord<z.ZodEnum<{
98
+ browser: "browser";
99
+ campaign: "campaign";
100
+ city: "city";
101
+ country: "country";
102
+ date: "date";
103
+ deviceCategory: "deviceCategory";
104
+ hostname: "hostname";
105
+ medium: "medium";
106
+ operatingSystem: "operatingSystem";
107
+ page: "page";
108
+ region: "region";
109
+ source: "source";
110
+ }> & z.core.$partial, z.ZodString>;
111
+ metrics: z.ZodRecord<z.ZodEnum<{
112
+ avgSessionDuration: "avgSessionDuration";
113
+ bounceRate: "bounceRate";
114
+ conversions: "conversions";
115
+ engagementRate: "engagementRate";
116
+ keyEvents: "keyEvents";
117
+ pageviews: "pageviews";
118
+ sessions: "sessions";
119
+ users: "users";
120
+ }> & z.core.$partial, z.ZodNumber>;
121
+ cloudflare: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
122
+ bot: "bot";
123
+ deliveryType: "deliveryType";
124
+ navigationType: "navigationType";
125
+ }> & z.core.$partial, z.ZodString>>;
126
+ ga4: z.ZodOptional<z.ZodObject<{
127
+ dimensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
128
+ metrics: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
129
+ }, z.core.$strip>>;
130
+ posthog: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
131
+ }, z.core.$strip>;
132
+ export type AnalyticsRow = z.infer<typeof analyticsRowSchema>;
133
+ /**
134
+ * Cross-provider filter operators. Kept to the set every provider can serve so
135
+ * a portable filter degrades predictably:
136
+ * - `is` / `isNot` — exact-match (and its negation).
137
+ * - `in` — match any of a value list.
138
+ * - `contains` — substring match.
139
+ * Regex / OR-groups are intentionally absent: Cloudflare RUM can't express them,
140
+ * so they would not be portable. Reach for the per-provider escape hatch instead.
141
+ */
142
+ export declare const analyticsFilterOpSchema: z.ZodEnum<{
143
+ contains: "contains";
144
+ in: "in";
145
+ is: "is";
146
+ isNot: "isNot";
147
+ }>;
148
+ export type AnalyticsFilterOp = z.infer<typeof analyticsFilterOpSchema>;
149
+ /**
150
+ * One portable filter predicate over a generic dimension. Translated per
151
+ * provider (GA4 `dimensionFilter`, Cloudflare GraphQL filter keys, PostHog HogQL
152
+ * `WHERE`). A provider with no field for `dimension` drops the predicate rather
153
+ * than erroring — same graceful-degrade contract as dimensions resolving to `''`
154
+ * (e.g. Cloudflare RUM has no `medium`/`campaign`, so those filters are no-ops
155
+ * there). `value` is a single string for `is`/`isNot`/`contains` and a list for
156
+ * `in` (a bare string is accepted and treated as a one-element list).
157
+ */
158
+ export declare const analyticsFilterSchema: z.ZodObject<{
159
+ dimension: z.ZodEnum<{
160
+ browser: "browser";
161
+ campaign: "campaign";
162
+ city: "city";
163
+ country: "country";
164
+ date: "date";
165
+ deviceCategory: "deviceCategory";
166
+ hostname: "hostname";
167
+ medium: "medium";
168
+ operatingSystem: "operatingSystem";
169
+ page: "page";
170
+ region: "region";
171
+ source: "source";
172
+ }>;
173
+ op: z.ZodEnum<{
174
+ contains: "contains";
175
+ in: "in";
176
+ is: "is";
177
+ isNot: "isNot";
178
+ }>;
179
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
180
+ caseInsensitive: z.ZodDefault<z.ZodBoolean>;
181
+ }, z.core.$strip>;
182
+ export type AnalyticsFilter = z.input<typeof analyticsFilterSchema>;
183
+ /**
184
+ * A provider-agnostic query. The provider client owns translation into GA4's
185
+ * `runReport` body or Cloudflare's GraphQL document.
186
+ */
187
+ export declare const analyticsQuerySchema: z.ZodObject<{
188
+ provider: z.ZodEnum<{
189
+ cloudflare: "cloudflare";
190
+ ga4: "ga4";
191
+ plausible: "plausible";
192
+ posthog: "posthog";
193
+ umami: "umami";
194
+ }>;
195
+ propertyId: z.ZodString;
196
+ dimensions: z.ZodArray<z.ZodEnum<{
197
+ browser: "browser";
198
+ campaign: "campaign";
199
+ city: "city";
200
+ country: "country";
201
+ date: "date";
202
+ deviceCategory: "deviceCategory";
203
+ hostname: "hostname";
204
+ medium: "medium";
205
+ operatingSystem: "operatingSystem";
206
+ page: "page";
207
+ region: "region";
208
+ source: "source";
209
+ }>>;
210
+ metrics: z.ZodArray<z.ZodEnum<{
211
+ avgSessionDuration: "avgSessionDuration";
212
+ bounceRate: "bounceRate";
213
+ conversions: "conversions";
214
+ engagementRate: "engagementRate";
215
+ keyEvents: "keyEvents";
216
+ pageviews: "pageviews";
217
+ sessions: "sessions";
218
+ users: "users";
219
+ }>>;
220
+ dateRange: z.ZodObject<{
221
+ from: z.ZodString;
222
+ to: z.ZodString;
223
+ }, z.core.$strip>;
224
+ limit: z.ZodOptional<z.ZodNumber>;
225
+ excludeBots: z.ZodDefault<z.ZodBoolean>;
226
+ filters: z.ZodDefault<z.ZodArray<z.ZodObject<{
227
+ dimension: z.ZodEnum<{
228
+ browser: "browser";
229
+ campaign: "campaign";
230
+ city: "city";
231
+ country: "country";
232
+ date: "date";
233
+ deviceCategory: "deviceCategory";
234
+ hostname: "hostname";
235
+ medium: "medium";
236
+ operatingSystem: "operatingSystem";
237
+ page: "page";
238
+ region: "region";
239
+ source: "source";
240
+ }>;
241
+ op: z.ZodEnum<{
242
+ contains: "contains";
243
+ in: "in";
244
+ is: "is";
245
+ isNot: "isNot";
246
+ }>;
247
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
248
+ caseInsensitive: z.ZodDefault<z.ZodBoolean>;
249
+ }, z.core.$strip>>>;
250
+ cloudflare: z.ZodOptional<z.ZodObject<{
251
+ dimensions: z.ZodArray<z.ZodEnum<{
252
+ bot: "bot";
253
+ deliveryType: "deliveryType";
254
+ navigationType: "navigationType";
255
+ }>>;
256
+ }, z.core.$strip>>;
257
+ ga4: z.ZodOptional<z.ZodObject<{
258
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
259
+ metrics: z.ZodOptional<z.ZodArray<z.ZodString>>;
260
+ }, z.core.$strip>>;
261
+ posthog: z.ZodOptional<z.ZodObject<{
262
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
263
+ }, z.core.$strip>>;
264
+ }, z.core.$strip>;
265
+ export type AnalyticsQuery = z.input<typeof analyticsQuerySchema>;
@@ -0,0 +1,175 @@
1
+ import { z } from 'zod';
2
+ // The normalized, provider-agnostic data shape for Web Analytics. GA4 and
3
+ // Cloudflare provider clients reshape their raw API responses onto `AnalyticsRow`.
4
+ // Generic — carries no Pro or nuxtseo.com concepts. See ADR-0032 §2.
5
+ export const analyticsProviderSchema = z.enum(['ga4', 'cloudflare', 'posthog', 'umami', 'plausible']);
6
+ /**
7
+ * Dimensions every provider can be asked to group by. `source`/`medium`/
8
+ * `campaign` are the UTM-style traffic-attribution triple — a provider with no
9
+ * analog for one of them (Cloudflare RUM has only a referrer host) resolves it
10
+ * to an empty string rather than erroring, mirroring how metrics resolve to 0.
11
+ * Provider-native dimensions with no cross-provider analog go through the
12
+ * per-provider escape hatch on the query, not this enum.
13
+ */
14
+ export const analyticsDimensionSchema = z.enum([
15
+ 'date',
16
+ 'page',
17
+ 'hostname',
18
+ 'source',
19
+ 'medium',
20
+ 'campaign',
21
+ 'country',
22
+ 'region',
23
+ 'city',
24
+ 'deviceCategory',
25
+ 'browser',
26
+ 'operatingSystem',
27
+ ]);
28
+ /**
29
+ * Metrics every provider maps onto. Cloudflare Web Analytics has no concept of
30
+ * conversions/keyEvents; those resolve to 0 rather than erroring.
31
+ */
32
+ export const analyticsMetricSchema = z.enum([
33
+ 'sessions',
34
+ 'users',
35
+ 'pageviews',
36
+ 'conversions',
37
+ 'keyEvents',
38
+ 'engagementRate',
39
+ 'bounceRate',
40
+ 'avgSessionDuration',
41
+ ]);
42
+ /**
43
+ * Cloudflare-only RUM dimensions with no cross-provider analog. Kept out of the
44
+ * generic `analyticsDimensionSchema` (they would be dead weight on GA4); reached
45
+ * through the opt-in `cloudflare` escape hatch on the query and the row. The enum
46
+ * values are the raw Cloudflare RUM field names.
47
+ */
48
+ export const cloudflareDimensionSchema = z.enum([
49
+ 'navigationType',
50
+ 'deliveryType',
51
+ 'bot',
52
+ ]);
53
+ // --- Provider escape hatches -----------------------------------------------
54
+ //
55
+ // Each provider may be opted into raw provider-native fields that have no
56
+ // cross-provider analog. Field names are validated, never bare strings:
57
+ // - Cloudflare RUM dimensions are a closed enum (`cloudflareDimensionSchema`).
58
+ // - GA4 Data API names are word chars plus `:` — the `:` admits custom
59
+ // `customEvent:*` / `customUser:*` dimensions; the set is otherwise open.
60
+ // - PostHog hatch values are HogQL `$pageview` property expressions, dotted
61
+ // `$`-identifiers. The regex doubles as a HogQL injection guard, since the
62
+ // expression is interpolated into the generated SQL.
63
+ const ga4FieldNameSchema = z.string().regex(/^[\w:]+$/, 'invalid GA4 field name');
64
+ const posthogPropertyExprSchema = z.string().regex(/^[\w$.]+$/, 'invalid PostHog property expression');
65
+ /** Query-side hatch — opt a provider into raw native dimensions/metrics. */
66
+ export const cloudflareQueryHatchSchema = z.object({
67
+ dimensions: z.array(cloudflareDimensionSchema),
68
+ });
69
+ export const ga4QueryHatchSchema = z.object({
70
+ dimensions: z.array(ga4FieldNameSchema).optional(),
71
+ metrics: z.array(ga4FieldNameSchema).optional(),
72
+ });
73
+ export const posthogQueryHatchSchema = z.object({
74
+ dimensions: z.array(posthogPropertyExprSchema).optional(),
75
+ });
76
+ /** Row-side hatch — raw native values grouped onto one result row. */
77
+ export const cloudflareRowHatchSchema = z.partialRecord(cloudflareDimensionSchema, z.string());
78
+ export const ga4RowHatchSchema = z.object({
79
+ dimensions: z.record(ga4FieldNameSchema, z.string()).optional(),
80
+ metrics: z.record(ga4FieldNameSchema, z.number()).optional(),
81
+ });
82
+ export const posthogRowHatchSchema = z.record(posthogPropertyExprSchema, z.string());
83
+ /**
84
+ * One grouped result: dimension values keyed by name, metric values keyed by
85
+ * name. Partial records — a row only carries what its query asked for.
86
+ */
87
+ export const analyticsRowSchema = z.object({
88
+ dimensions: z.partialRecord(analyticsDimensionSchema, z.string()),
89
+ metrics: z.partialRecord(analyticsMetricSchema, z.number()),
90
+ /**
91
+ * Raw provider-native values — present only when the query opted into a
92
+ * provider escape hatch (`query.cloudflare` / `query.ga4` / `query.posthog`),
93
+ * and only on rows from that provider. Keyed by the raw provider field name.
94
+ * Cloudflare's keys are a closed enum; GA4's and PostHog's are open string
95
+ * records because those providers expose unbounded (incl. custom) fields.
96
+ */
97
+ cloudflare: cloudflareRowHatchSchema.optional(),
98
+ ga4: ga4RowHatchSchema.optional(),
99
+ posthog: posthogRowHatchSchema.optional(),
100
+ });
101
+ /**
102
+ * Cross-provider filter operators. Kept to the set every provider can serve so
103
+ * a portable filter degrades predictably:
104
+ * - `is` / `isNot` — exact-match (and its negation).
105
+ * - `in` — match any of a value list.
106
+ * - `contains` — substring match.
107
+ * Regex / OR-groups are intentionally absent: Cloudflare RUM can't express them,
108
+ * so they would not be portable. Reach for the per-provider escape hatch instead.
109
+ */
110
+ export const analyticsFilterOpSchema = z.enum(['is', 'isNot', 'in', 'contains']);
111
+ /**
112
+ * One portable filter predicate over a generic dimension. Translated per
113
+ * provider (GA4 `dimensionFilter`, Cloudflare GraphQL filter keys, PostHog HogQL
114
+ * `WHERE`). A provider with no field for `dimension` drops the predicate rather
115
+ * than erroring — same graceful-degrade contract as dimensions resolving to `''`
116
+ * (e.g. Cloudflare RUM has no `medium`/`campaign`, so those filters are no-ops
117
+ * there). `value` is a single string for `is`/`isNot`/`contains` and a list for
118
+ * `in` (a bare string is accepted and treated as a one-element list).
119
+ */
120
+ export const analyticsFilterSchema = z.object({
121
+ dimension: analyticsDimensionSchema,
122
+ op: analyticsFilterOpSchema,
123
+ value: z.union([z.string(), z.array(z.string())]),
124
+ /**
125
+ * Match case-insensitively. Defaults true so cross-provider dimension values
126
+ * with inconsistent casing (e.g. PostHog `$device_type` is `Desktop`, GA4
127
+ * `deviceCategory` is `desktop`) match the same filter. Cloudflare RUM has no
128
+ * case-insensitive operator, so it is best-effort there.
129
+ */
130
+ caseInsensitive: z.boolean().default(true),
131
+ });
132
+ /**
133
+ * A provider-agnostic query. The provider client owns translation into GA4's
134
+ * `runReport` body or Cloudflare's GraphQL document.
135
+ */
136
+ export const analyticsQuerySchema = z.object({
137
+ provider: analyticsProviderSchema,
138
+ /** GA4 property id or Cloudflare Web Analytics site tag. */
139
+ propertyId: z.string().min(1),
140
+ dimensions: z.array(analyticsDimensionSchema),
141
+ metrics: z.array(analyticsMetricSchema).min(1),
142
+ dateRange: z.object({
143
+ from: z.string(), // ISO yyyy-mm-dd
144
+ to: z.string(),
145
+ }),
146
+ limit: z.number().int().positive().optional(),
147
+ /**
148
+ * Exclude automated traffic. Default true. Cloudflare applies a `bot: 0`
149
+ * filter; GA4 already excludes known bots from every report, so it is a
150
+ * no-op there.
151
+ */
152
+ excludeBots: z.boolean().default(true),
153
+ /**
154
+ * Portable dimension filters, ANDed together and applied on top of the date
155
+ * window + `excludeBots`. Each provider translates what it can and silently
156
+ * drops predicates over dimensions it has no field for (see
157
+ * `analyticsFilterSchema`). Empty by default (no filtering).
158
+ */
159
+ filters: z.array(analyticsFilterSchema).default([]),
160
+ /**
161
+ * Provider-specific escape hatches. Each opts the matching provider into raw
162
+ * provider-native fields with no cross-provider analog; the grouped values
163
+ * come back on the matching key of `AnalyticsRow`. A provider ignores hatches
164
+ * addressed to other providers.
165
+ *
166
+ * - `cloudflare` — Cloudflare-only RUM dimensions (closed enum).
167
+ * - `ga4` — raw GA4 Data API dimension/metric names, including custom
168
+ * `customEvent:*` / `customUser:*` dimensions.
169
+ * - `posthog` — raw HogQL `$pageview` property expressions (e.g.
170
+ * `properties.$browser_version`); validated as dotted identifiers.
171
+ */
172
+ cloudflare: cloudflareQueryHatchSchema.optional(),
173
+ ga4: ga4QueryHatchSchema.optional(),
174
+ posthog: posthogQueryHatchSchema.optional(),
175
+ });
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ export declare const analyticsWebVitalsRowSchema: z.ZodObject<{
3
+ date: z.ZodString;
4
+ lcpP75: z.ZodNullable<z.ZodNumber>;
5
+ inpP75: z.ZodNullable<z.ZodNumber>;
6
+ clsP75: z.ZodNullable<z.ZodNumber>;
7
+ lcpGood: z.ZodNumber;
8
+ lcpNeedsImprovement: z.ZodNumber;
9
+ lcpPoor: z.ZodNumber;
10
+ inpGood: z.ZodNumber;
11
+ inpNeedsImprovement: z.ZodNumber;
12
+ inpPoor: z.ZodNumber;
13
+ clsGood: z.ZodNumber;
14
+ clsNeedsImprovement: z.ZodNumber;
15
+ clsPoor: z.ZodNumber;
16
+ }, z.core.$strip>;
17
+ export type AnalyticsWebVitalsRow = z.infer<typeof analyticsWebVitalsRowSchema>;
18
+ /** The three Core Web Vitals a RUM dataset attributes to a DOM element. */
19
+ export declare const webVitalsMetricSchema: z.ZodEnum<{
20
+ cls: "cls";
21
+ inp: "inp";
22
+ lcp: "lcp";
23
+ }>;
24
+ export type WebVitalsMetric = z.infer<typeof webVitalsMetricSchema>;
25
+ export declare const analyticsWebVitalsElementSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
26
+ route: z.ZodString;
27
+ selector: z.ZodString;
28
+ p75: z.ZodNullable<z.ZodNumber>;
29
+ count: z.ZodNumber;
30
+ poorFraction: z.ZodNullable<z.ZodNumber>;
31
+ metric: z.ZodLiteral<"lcp">;
32
+ resource: z.ZodNullable<z.ZodString>;
33
+ fetchPriority: z.ZodNullable<z.ZodString>;
34
+ initiatorType: z.ZodNullable<z.ZodString>;
35
+ phases: z.ZodObject<{
36
+ timeToFirstByte: z.ZodNullable<z.ZodNumber>;
37
+ resourceLoadDelay: z.ZodNullable<z.ZodNumber>;
38
+ resourceLoadTime: z.ZodNullable<z.ZodNumber>;
39
+ elementRenderDelay: z.ZodNullable<z.ZodNumber>;
40
+ }, z.core.$strip>;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ route: z.ZodString;
43
+ selector: z.ZodString;
44
+ p75: z.ZodNullable<z.ZodNumber>;
45
+ count: z.ZodNumber;
46
+ poorFraction: z.ZodNullable<z.ZodNumber>;
47
+ metric: z.ZodLiteral<"inp">;
48
+ actionName: z.ZodNullable<z.ZodString>;
49
+ }, z.core.$strip>, z.ZodObject<{
50
+ route: z.ZodString;
51
+ selector: z.ZodString;
52
+ p75: z.ZodNullable<z.ZodNumber>;
53
+ count: z.ZodNumber;
54
+ poorFraction: z.ZodNullable<z.ZodNumber>;
55
+ metric: z.ZodLiteral<"cls">;
56
+ }, z.core.$strip>], "metric">;
57
+ export type AnalyticsWebVitalsElement = z.infer<typeof analyticsWebVitalsElementSchema>;
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+ // Field-data Core Web Vitals from a provider's RUM dataset (real users), one row
3
+ // per day. Distinct from the traffic metrics in `analytics-row` — web vitals are
4
+ // p75 quantiles + good/needs-improvement/poor distribution counts, not summable
5
+ // counts. Only providers with a RUM web-vitals dataset emit these (Cloudflare).
6
+ // The shape mirrors the Pro CrUX `CwvHistoryRow` so a consumer can drop it into
7
+ // the existing Core Web Vitals surface as a fallback when CrUX has no data.
8
+ export const analyticsWebVitalsRowSchema = z.object({
9
+ /** ISO `yyyy-mm-dd`. */
10
+ date: z.string(),
11
+ /** p75 — the headline CrUX-parity figure. Null when the metric has no samples. */
12
+ lcpP75: z.number().nullable(),
13
+ inpP75: z.number().nullable(),
14
+ clsP75: z.number().nullable(),
15
+ // Provider observations behind the good/needs-improvement/poor histogram.
16
+ // Adaptive-provider estimates are normalized to their effective sample size.
17
+ lcpGood: z.number(),
18
+ lcpNeedsImprovement: z.number(),
19
+ lcpPoor: z.number(),
20
+ inpGood: z.number(),
21
+ inpNeedsImprovement: z.number(),
22
+ inpPoor: z.number(),
23
+ clsGood: z.number(),
24
+ clsNeedsImprovement: z.number(),
25
+ clsPoor: z.number(),
26
+ });
27
+ /** The three Core Web Vitals a RUM dataset attributes to a DOM element. */
28
+ export const webVitalsMetricSchema = z.enum(['lcp', 'inp', 'cls']);
29
+ // Per-element field-CWV attribution — one DOM element behind a metric, from the
30
+ // provider's RUM dataset, discriminated by `metric`. This is the "what to fix"
31
+ // payload: `selector` is the element's DOM path, grep-able in the user's own
32
+ // source. LCP additionally carries the resource + fetch-priority + phase split
33
+ // (so a text-LCP vs image-LCP fix can be told apart); INP carries the interaction
34
+ // action; CLS carries only the shifted element. `count` is a provider-sampled
35
+ // estimate (Cloudflare adaptive sampling), never exact. Rank by severity and
36
+ // label it as estimated.
37
+ const elementBase = {
38
+ /** Page path where the element was observed (`requestPath`). */
39
+ route: z.string(),
40
+ /** DOM selector of the element — grep-able in the user's source. */
41
+ selector: z.string(),
42
+ /** p75: ms for lcp/inp, unitless for cls. Null when the group has no samples. */
43
+ p75: z.number().nullable(),
44
+ /** Sampled estimate of affected page views, not an exact count. */
45
+ count: z.number(),
46
+ /** poor / (good + ni + poor) for this element, or null when no distribution. */
47
+ poorFraction: z.number().nullable(),
48
+ };
49
+ export const analyticsWebVitalsElementSchema = z.discriminatedUnion('metric', [
50
+ z.object({
51
+ metric: z.literal('lcp'),
52
+ ...elementBase,
53
+ /** Host+path of the LCP resource. Null = text LCP (heading/paragraph). */
54
+ resource: z.string().nullable(),
55
+ /** `fetchpriority` the browser saw on the LCP element. Null when n/a. */
56
+ fetchPriority: z.string().nullable(),
57
+ initiatorType: z.string().nullable(),
58
+ /** The four LCP sub-phases (ms averages) — which one dominates names the fix. */
59
+ phases: z.object({
60
+ timeToFirstByte: z.number().nullable(),
61
+ resourceLoadDelay: z.number().nullable(),
62
+ resourceLoadTime: z.number().nullable(),
63
+ elementRenderDelay: z.number().nullable(),
64
+ }),
65
+ }),
66
+ z.object({
67
+ metric: z.literal('inp'),
68
+ ...elementBase,
69
+ /** Interaction type behind the delay (pointer/keyboard/…). */
70
+ actionName: z.string().nullable(),
71
+ }),
72
+ z.object({
73
+ metric: z.literal('cls'),
74
+ ...elementBase,
75
+ }),
76
+ ]);
@@ -0,0 +1,35 @@
1
+ import type { AnalyticsDimension, AnalyticsMetric, AnalyticsProvider } from './analytics-row.js';
2
+ export interface AnalyticsCapabilities {
3
+ /** Aggregate (dimensionless) metrics that carry real data; others resolve to 0. */
4
+ aggregateMetrics: AnalyticsMetric[];
5
+ /**
6
+ * Count metrics with a real per-day series. A subset of `{sessions, users,
7
+ * pageviews}` — drives the chartable series. Cloudflare's `users` equals
8
+ * `sessions` (drop the duplicate); Umami has no per-day visitors series.
9
+ */
10
+ seriesMetrics: AnalyticsMetric[];
11
+ /** Dimensions with a real breakdown; others return an empty list. */
12
+ breakdownDimensions: AnalyticsDimension[];
13
+ /**
14
+ * Count metrics populated PER breakdown row. Umami's `/metrics` returns a
15
+ * visitor count only (`['users']`); everything else returns the full
16
+ * `{sessions, users, pageviews}`. Drives breakdown ranking + visible columns.
17
+ */
18
+ breakdownMetrics: AnalyticsMetric[];
19
+ /** The session-quality metric the provider reports, or null (neither). */
20
+ quality: 'engagementRate' | 'bounceRate' | null;
21
+ /** Field-data Core Web Vitals (RUM) dataset available (`queryWebVitals`). */
22
+ webVitals: boolean;
23
+ /** Key events / conversions available. */
24
+ keyEvents: boolean;
25
+ }
26
+ export declare const ANALYTICS_CAPABILITIES: Record<AnalyticsProvider, AnalyticsCapabilities>;
27
+ export declare function getAnalyticsCapabilities(provider: string | null | undefined): AnalyticsCapabilities | null;
28
+ /**
29
+ * The metric to rank/show a breakdown by. Returns `preferred` when the provider
30
+ * populates it per row (the common case: pages by pageviews, else sessions),
31
+ * otherwise the provider's single available breakdown metric — Umami serves a
32
+ * visitor count only, so every breakdown there ranks by `users`. An unknown
33
+ * provider keeps `preferred`.
34
+ */
35
+ export declare function resolveBreakdownMetric(provider: string | null | undefined, preferred: AnalyticsMetric): AnalyticsMetric;