{"id":51317902,"url":"https://github.com/vllnt/convex-analytics","last_synced_at":"2026-07-01T09:31:15.856Z","repository":{"id":365105411,"uuid":"1187339077","full_name":"vllnt/convex-analytics","owner":"vllnt","description":"Convex Component — full-featured API-first analytics engine with events, sessions, funnels, retention, and MCP tools. Reusable via app.use().","archived":false,"fork":false,"pushed_at":"2026-06-15T21:52:00.000Z","size":352,"stargazers_count":1,"open_issues_count":15,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T23:21:13.601Z","etag":null,"topics":["analytics","convex","convex-component","event-tracking","first-party-analytics","mcp","product-analytics","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vllnt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"bntvllnt"}},"created_at":"2026-03-20T16:01:03.000Z","updated_at":"2026-06-15T22:32:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vllnt/convex-analytics","commit_stats":null,"previous_names":["vllnt/convex-analytics"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/vllnt/convex-analytics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-analytics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-analytics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-analytics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-analytics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vllnt","download_url":"https://codeload.github.com/vllnt/convex-analytics/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-analytics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35001648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","convex","convex-component","event-tracking","first-party-analytics","mcp","product-analytics","typescript"],"created_at":"2026-07-01T09:31:15.272Z","updated_at":"2026-07-01T09:31:15.848Z","avatar_url":"https://github.com/vllnt.png","language":"TypeScript","funding_links":["https://github.com/sponsors/bntvllnt"],"categories":[],"sub_categories":[],"readme":"\u003c!-- Badges --\u003e\n[![convex-component](https://img.shields.io/badge/convex-component-EE342F.svg)](https://www.convex.dev/components)\n[![npm](https://img.shields.io/npm/v/@vllnt/convex-analytics.svg)](https://www.npmjs.com/package/@vllnt/convex-analytics)\n[![CI](https://github.com/vllnt/convex-analytics/actions/workflows/ci.yml/badge.svg)](https://github.com/vllnt/convex-analytics/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/@vllnt/convex-analytics.svg)](LICENSE)\n\n# @vllnt/convex-analytics\n\nA generic, configurable, embedded analytics component for Convex — any event,\nany dimension, any domain — with rollup-on-write so reads stay O(1).\n\n```ts\nimport { AnalyticsClient } from \"@vllnt/convex-analytics\";\nimport { components } from \"./_generated/api\";\n\nconst analytics = new AnalyticsClient(components.analytics, {\n  dimensions: [\"plan\", \"country\"], // prop keys to roll up on\n});\n\nawait analytics.track(ctx, \"signup\", { subjectRef: userId, props: { plan: \"pro\", country: \"FR\" } });\nconst byPlan = await analytics.top(ctx, \"signup\", \"plan\"); // [{ value: \"pro\", count: 1 }]\n```\n\n## Features\n\n- **Generic events** — free-string event name, opaque `subjectRef` / `sessionRef`, host-typed `props`.\n- **Host-declared dimensions** — you pass the prop keys to roll up on; nothing is hardcoded.\n- **Rollup-on-write** — counts are incremented as events land, so `metric` / `top` / `timeseries` read in O(1) (backed by `@convex-dev/aggregate` + `@convex-dev/sharded-counter`).\n- **Rich verb set** — `metric`, `top`, `timeseries`, `uniques`, `funnel`, `retention`, `distribution`, plus paginated raw `list`.\n- **Configurable** — `scope`, `dimensions`, `granularities`, `retentionDays`, `sampleRate`, `propsValidator`; sensible defaults, zero config required.\n- **Per-session rate limiting + dedupe + sampling** built into `track`.\n- **Opt-in web preset** — `@vllnt/convex-analytics/web` adds web dimensions + UA/geo helpers when you want them.\n- **Optional React hooks** — `@vllnt/convex-analytics/react` for reactive aggregate reads.\n- **Zero hardcoded domain** — no `v.any()`, no baked-in web fields; the host owns meaning and auth.\n\n## Generic core vs web preset\n\nThe core is domain-neutral: it knows nothing about web, mobile, or any vertical.\nWeb analytics — pageview dimensions (`path`, `referrer`, `device`, `browser`, `os`,\n`country`, UTM) plus `parseUserAgent` / `geoFromHeaders` / `trackPageview` helpers —\nlives in the opt-in `@vllnt/convex-analytics/web` preset. You turn it on by passing\n`webDimensions` to the client's `dimensions` config; otherwise the core ships no web\nfields. See [docs/web-preset.md](docs/web-preset.md).\n\n## Installation\n\n```bash\nnpm install @vllnt/convex-analytics\n```\n\n## Usage\n\n```ts\n// convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\nimport analytics from \"@vllnt/convex-analytics/convex.config\";\n\nconst app = defineApp();\napp.use(analytics);\nexport default app;\n```\n\n```ts\n// convex/analytics.ts — track + read, server-side\nimport { AnalyticsClient } from \"@vllnt/convex-analytics\";\nimport { components } from \"./_generated/api\";\n\ntype MyProps = { plan?: string; country?: string };\n\nconst analytics = new AnalyticsClient\u003cMyProps\u003e(components.analytics, {\n  dimensions: [\"plan\", \"country\"],\n  granularities: [\"day\"],\n});\n\nawait analytics.track(ctx, \"signup\", { subjectRef: userId, props: { plan: \"pro\" } });\nconst total = await analytics.metric(ctx, \"signup\");\n```\n\n## Config\n\nPassed to the `AnalyticsClient` constructor. All optional — the defaults work with zero config.\n\n| Option | Type | Default | Purpose |\n|--------|------|---------|---------|\n| `scope` | `string` | `\"default\"` | Multi-tenant partition; isolates one tenant's data. |\n| `dimensions` | `string[]` | `[]` | Prop keys to roll up on (drives rollup-on-write). Empty = count by event name only. |\n| `granularities` | `(\"minute\" \\| \"hour\" \\| \"day\")[]` | `[\"day\"]` | Rollup bucket sizes (`minute` for short live windows). |\n| `retentionDays` | `number` | `90` | Raw-event TTL in days (rollups are kept forever). Applied via `configure`. |\n| `sampleRate` | `number` | `1` | Fraction `0..1` of events to keep at ingest. |\n| `sessionIdleMs` | `number` | `1800000` | Idle timeout before a session is closed. Applied via `configure`. |\n| `propsValidator` | Convex validator | typed scalar record | Optional host validator narrowing `props` at the boundary. |\n\n`scope`, `dimensions`, `granularities`, and `sampleRate` apply per call from the client.\n`retentionDays`, `sampleRate`, and `sessionIdleMs` are persisted for the prune/close-sessions\ncrons by calling `analytics.configure(ctx, { ... })` once.\n\n## API\n\n| Verb | Kind | Purpose |\n|------|------|---------|\n| `track(ctx, name, opts)` | mutation | Ingest an event; rollup-on-write + raw event + counter. Returns `\"tracked\" \\| \"dropped\" \\| \"duplicate\"`. |\n| `metric(ctx, name, opts)` | query | Total count over a range, optionally filtered by a dimension value. |\n| `top(ctx, name, dimension, opts)` | query | Top values of a dimension (breakdown). |\n| `timeseries(ctx, name, opts)` | query | Bucketed counts over a range. |\n| `uniques(ctx, opts)` | query | DAU / WAU / MAU from subjects. |\n| `funnel(ctx, steps, opts)` | query | Ordered step conversion, keyed by `subjectRef`. |\n| `retention(ctx, opts)` | query | Cohort return rates by first-seen period. |\n| `distribution(ctx, name, measure, opts)` | query | Histogram of a numeric measure over declared buckets + overflow (with `count` / `sum`). |\n| `list(ctx, name, paginationOpts, opts)` | query | Paginated raw events, newest first. |\n| `configure(ctx, opts)` | mutation | Persist cron-relevant config (retention / sampling / idle). |\n\nFull reference: [docs/client-sdk.md](docs/client-sdk.md). REST surface: [docs/api-reference.md](docs/api-reference.md).\n\n## What it serves\n\nThe Convex-native, authoritative in-app aggregate layer — predefined-dimension rollups served\n**reactively** in O(1). It complements a product-analytics warehouse; it doesn't replace one.\nDeclare the dimensions and measures you'll query and the rollups answer instantly; ad-hoc or\nretroactive questions over **non-declared** dimensions, or beyond the raw-event retention window,\nare an export concern. `metric` / `top` / `timeseries` read pre-aggregated rollups (kept forever);\n`funnel` / `retention` / `distribution` read raw events in range (index-backed, bounded, TTL-pruned).\n\n## Web preset\n\n```ts\nimport { AnalyticsClient } from \"@vllnt/convex-analytics\";\nimport { webDimensions, trackPageview } from \"@vllnt/convex-analytics/web\";\n\nconst analytics = new AnalyticsClient(components.analytics, { dimensions: webDimensions });\n\nawait trackPageview(analytics, ctx, {\n  path: \"/pricing\",\n  ua: request.headers.get(\"user-agent\") ?? undefined,\n  headers: request.headers,\n});\n```\n\n`webDimensions` opts the rollups into `path` / `referrer` / `device` / `browser` / `os` /\n`country` / UTM; `parseUserAgent` and `geoFromHeaders` build those props from a request.\nSee [docs/web-preset.md](docs/web-preset.md).\n\n## React\n\n```tsx\nimport { useMetric, useTop } from \"@vllnt/convex-analytics/react\";\nimport { api } from \"../convex/_generated/api\";\n\nconst signups = useMetric(api.analytics.metric, { name: \"signup\" });\nconst byPlan = useTop(api.analytics.top, { name: \"signup\", dimension: \"plan\" });\n```\n\nThin reactive wrappers over the host's re-exported query refs (`metric`, `top`, `timeseries`,\n`uniques`). `react` + `convex` are optional peer deps; backend-only consumers pull no React.\n\n## MCP (Claude Code)\n\n```bash\nclaude mcp add convex-analytics-mcp \\\n  --env CONVEX_URL=https://your-deployment.convex.cloud \\\n  --env ANALYTICS_API_KEY=your-key\n```\n\n7 tools: `track`, `get_metric`, `get_top`, `get_timeseries`, `get_uniques`, `detect_anomalies`,\n`query_analytics` (NL router). See [docs/mcp-tools.md](docs/mcp-tools.md).\n\n## Security\n\n- Auth-agnostic mount — the host gates access and passes opaque `subjectRef` / `sessionRef` in.\n- Tables are sandboxed; the host reaches them only through the exported functions.\n- REST endpoints require an `x-api-key` header, compared timing-safe.\n\nSee [docs/architecture.md](docs/architecture.md).\n\n## Testing\n\n```bash\npnpm test\n```\n\n```ts\nimport analyticsTest from \"@vllnt/convex-analytics/test\";\nimport { convexTest } from \"convex-test\";\n\nconst t = convexTest(hostSchema, hostModules);\nanalyticsTest.register(t);\n```\n\n## Documentation\n\n[Schema](docs/schema.md) · [Client SDK](docs/client-sdk.md) · [REST API](docs/api-reference.md) ·\n[Web Preset](docs/web-preset.md) · [Multi-Product](docs/multi-product.md) ·\n[Architecture](docs/architecture.md) · [MCP Tools](docs/mcp-tools.md). For AI agents:\n[`llms.txt`](llms.txt) · [`llms-full.txt`](llms-full.txt).\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Author\n\nBuilt by [bntvllnt](https://github.com/bntvllnt) · [bntvllnt.com](https://bntvllnt.com) · [X @bntvllnt](https://x.com/bntvllnt)\n\nPart of the [@vllnt](https://github.com/vllnt) Convex component fleet — [vllnt.com](https://vllnt.com)\n\nIf this is useful, [sponsor the work](https://github.com/sponsors/bntvllnt).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvllnt%2Fconvex-analytics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvllnt%2Fconvex-analytics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvllnt%2Fconvex-analytics/lists"}