import { z } from "zod"; import { PRODUCT_ANALYTICS_EVENT_NAMES } from "@app/shared-types"; export const MAX_ANALYTICS_BATCH_SIZE = 50; export const MAX_ANALYTICS_PROPERTY_SIZE = 8 * 1024; // 8 KiB per event properties JSON const eventNameSchema = z.enum(PRODUCT_ANALYTICS_EVENT_NAMES); const analyticsEventSchema = z.object({ name: eventNameSchema, occurredAt: z.string().refine((v) => !Number.isNaN(Date.parse(v))).optional(), anonymousId: z.string().max(64).optional(), sessionId: z.string().max(64).optional(), householdId: z.string().uuid().optional(), appVersion: z.string().max(32).optional(), platform: z.enum(["ios", "android", "web"]).optional(), locale: z.string().max(16).optional(), experimentVariant: z.string().max(128).optional(), properties: z.record(z.string(), z.unknown()).default({}), }); export const analyticsBatchInputSchema = z.object({ events: z .array(analyticsEventSchema) .min(1, "At least one event is required") .max(MAX_ANALYTICS_BATCH_SIZE, `Max ${MAX_ANALYTICS_BATCH_SIZE} events per batch`), }); export type AnalyticsBatchInput = z.infer; /** * Validate that the JSON-stringified properties of each event fit within the * per-event byte budget. Call this after zod parsing. */ export function validateAnalyticsPropertySize(events: AnalyticsBatchInput["events"]): void { for (let i = 0; i < events.length; i++) { const size = new Blob([JSON.stringify(events[i]?.properties ?? {})]).size; if (size > MAX_ANALYTICS_PROPERTY_SIZE) { throw new Error( `Event ${i} properties exceed ${MAX_ANALYTICS_PROPERTY_SIZE} bytes (${size})`, ); } } }