Embedded Convex analytics with rollup-on-write so metric, top, and timeseries reads stay O(1) — no external SaaS required.
npm install @vllnt/convex-analyticsAnalytics is a generic, configurable event-tracking component for Convex: any event name, any dimension, any domain — counts, breakdowns, timeseries, funnels, retention, and histograms, all readable in O(1) via rollup-on-write backed by `@convex-dev/aggregate` and `@convex-dev/sharded-counter`. Event data lives in sandboxed component tables, the host owns auth and passes opaque `subjectRef`/`sessionRef` refs in, and it runs identically on Convex Cloud and self-hosted convex-backend.
Install @vllnt/convex-analytics, mount it in convex.config.ts with app.use(analytics), and instantiate AnalyticsClient with the prop keys you want to roll up on. Calling analytics.track() increments rollup counters at write time, so analytics.metric(), analytics.top(), and analytics.timeseries() return pre-aggregated results in O(1) without scanning raw events.
AnalyticsClient exposes analytics.funnel(ctx, steps, opts) for ordered step conversion keyed by subjectRef and analytics.retention(ctx, opts) for cohort return rates by first-seen period. Both read from TTL-pruned raw events backed by indexes, so they stay bounded and do not require a separate data warehouse.
Import webDimensions and trackPageview from @vllnt/convex-analytics/web and pass webDimensions to the AnalyticsClient dimensions config. trackPageview builds path, referrer, device, browser, os, country, and UTM props from request headers using the bundled parseUserAgent and geoFromHeaders helpers, then calls track() on the core client.
@vllnt/convex-analytics ships an MCP server with seven tools including track, get_metric, get_top, get_timeseries, get_uniques, detect_anomalies, and query_analytics. Register it with Claude Code using claude mcp add, passing your CONVEX_URL and an ANALYTICS_API_KEY, and the agent can read and write analytics without direct database access.
@vllnt/convex-analytics uses a rollup-on-write strategy: every call to analytics.track() immediately increments counters per dimension value and time bucket, backed by @convex-dev/aggregate and @convex-dev/sharded-counter. The metric(), top(), and timeseries() queries read those pre-aggregated counters, so their cost is O(1) regardless of how many raw events have been ingested.
@vllnt/convex-analytics provides metric (total count over a range), top (dimension breakdowns), timeseries (bucketed counts), uniques (DAU/WAU/MAU), funnel (ordered step conversion by subjectRef), retention (cohort return rates), distribution (histogram of a numeric measure), and list (paginated raw events). metric, top, and timeseries read pre-aggregated rollups kept forever; funnel, retention, and distribution read TTL-pruned raw events via indexes.
The core of @vllnt/convex-analytics is domain-neutral and has no hardcoded web fields. You declare your own prop keys as dimensions, pass an optional propsValidator to type them at the boundary, and the component rolls up on whatever keys you specify. Web-specific dimensions and helpers are isolated in the opt-in @vllnt/convex-analytics/web preset.
@vllnt/convex-analytics is auth-agnostic: the host application gates access and passes opaque subjectRef and sessionRef values into track(). Tables are sandboxed so the host reaches them only through exported functions. Multi-tenancy is handled with the scope config option, which partitions one tenant's data from another without any additional schema work.
Raw events are pruned after a configurable retentionDays period (default 90 days), enforced by a cron configured via analytics.configure(). Rollup counters for metric(), top(), and timeseries() are kept indefinitely and are not affected by raw event pruning. Queries like funnel() and retention() that rely on raw events are bounded by the retention window.