npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vllnt/convex-analytics

v0.1.0

Published

Generic, configurable analytics component for Convex — any event, any dimension, any domain; rollup-on-write with funnels, retention, uniques

Readme

convex-component npm CI license

@vllnt/convex-analytics

A generic, configurable, embedded analytics component for Convex — any event, any dimension, any domain — with rollup-on-write so reads stay O(1).

import { AnalyticsClient } from "@vllnt/convex-analytics";
import { components } from "./_generated/api";

const analytics = new AnalyticsClient(components.analytics, {
  dimensions: ["plan", "country"], // prop keys to roll up on
});

await analytics.track(ctx, "signup", { subjectRef: userId, props: { plan: "pro", country: "FR" } });
const byPlan = await analytics.top(ctx, "signup", "plan"); // [{ value: "pro", count: 1 }]

Features

  • Generic events — free-string event name, opaque subjectRef / sessionRef, host-typed props.
  • Host-declared dimensions — you pass the prop keys to roll up on; nothing is hardcoded.
  • 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).
  • Rich verb set — metric, top, timeseries, uniques, funnel, retention, distribution, plus paginated raw list.
  • Configurable — scope, dimensions, granularities, retentionDays, sampleRate, propsValidator; sensible defaults, zero config required.
  • Per-session rate limiting + dedupe + sampling built into track.
  • Opt-in web preset — @vllnt/convex-analytics/web adds web dimensions + UA/geo helpers when you want them.
  • Optional React hooks — @vllnt/convex-analytics/react for reactive aggregate reads.
  • Zero hardcoded domain — no v.any(), no baked-in web fields; the host owns meaning and auth.

Generic core vs web preset

The core is domain-neutral: it knows nothing about web, mobile, or any vertical. Web analytics — pageview dimensions (path, referrer, device, browser, os, country, UTM) plus parseUserAgent / geoFromHeaders / trackPageview helpers — lives in the opt-in @vllnt/convex-analytics/web preset. You turn it on by passing webDimensions to the client's dimensions config; otherwise the core ships no web fields. See docs/web-preset.md.

Installation

npm install @vllnt/convex-analytics

Usage

// convex/convex.config.ts
import { defineApp } from "convex/server";
import analytics from "@vllnt/convex-analytics/convex.config";

const app = defineApp();
app.use(analytics);
export default app;
// convex/analytics.ts — track + read, server-side
import { AnalyticsClient } from "@vllnt/convex-analytics";
import { components } from "./_generated/api";

type MyProps = { plan?: string; country?: string };

const analytics = new AnalyticsClient<MyProps>(components.analytics, {
  dimensions: ["plan", "country"],
  granularities: ["day"],
});

await analytics.track(ctx, "signup", { subjectRef: userId, props: { plan: "pro" } });
const total = await analytics.metric(ctx, "signup");

Config

Passed to the AnalyticsClient constructor. All optional — the defaults work with zero config.

| Option | Type | Default | Purpose | |--------|------|---------|---------| | scope | string | "default" | Multi-tenant partition; isolates one tenant's data. | | dimensions | string[] | [] | Prop keys to roll up on (drives rollup-on-write). Empty = count by event name only. | | granularities | ("minute" \| "hour" \| "day")[] | ["day"] | Rollup bucket sizes (minute for short live windows). | | retentionDays | number | 90 | Raw-event TTL in days (rollups are kept forever). Applied via configure. | | sampleRate | number | 1 | Fraction 0..1 of events to keep at ingest. | | sessionIdleMs | number | 1800000 | Idle timeout before a session is closed. Applied via configure. | | propsValidator | Convex validator | typed scalar record | Optional host validator narrowing props at the boundary. |

scope, dimensions, granularities, and sampleRate apply per call from the client. retentionDays, sampleRate, and sessionIdleMs are persisted for the prune/close-sessions crons by calling analytics.configure(ctx, { ... }) once.

API

| Verb | Kind | Purpose | |------|------|---------| | track(ctx, name, opts) | mutation | Ingest an event; rollup-on-write + raw event + counter. Returns "tracked" \| "dropped" \| "duplicate". | | metric(ctx, name, opts) | query | Total count over a range, optionally filtered by a dimension value. | | top(ctx, name, dimension, opts) | query | Top values of a dimension (breakdown). | | timeseries(ctx, name, opts) | query | Bucketed counts over a range. | | uniques(ctx, opts) | query | DAU / WAU / MAU from subjects. | | funnel(ctx, steps, opts) | query | Ordered step conversion, keyed by subjectRef. | | retention(ctx, opts) | query | Cohort return rates by first-seen period. | | distribution(ctx, name, measure, opts) | query | Histogram of a numeric measure over declared buckets + overflow (with count / sum). | | list(ctx, name, paginationOpts, opts) | query | Paginated raw events, newest first. | | configure(ctx, opts) | mutation | Persist cron-relevant config (retention / sampling / idle). |

Full reference: docs/client-sdk.md. REST surface: docs/api-reference.md.

What it serves

The Convex-native, authoritative in-app aggregate layer — predefined-dimension rollups served reactively in O(1). It complements a product-analytics warehouse; it doesn't replace one. Declare the dimensions and measures you'll query and the rollups answer instantly; ad-hoc or retroactive questions over non-declared dimensions, or beyond the raw-event retention window, are an export concern. metric / top / timeseries read pre-aggregated rollups (kept forever); funnel / retention / distribution read raw events in range (index-backed, bounded, TTL-pruned).

Web preset

import { AnalyticsClient } from "@vllnt/convex-analytics";
import { webDimensions, trackPageview } from "@vllnt/convex-analytics/web";

const analytics = new AnalyticsClient(components.analytics, { dimensions: webDimensions });

await trackPageview(analytics, ctx, {
  path: "/pricing",
  ua: request.headers.get("user-agent") ?? undefined,
  headers: request.headers,
});

webDimensions opts the rollups into path / referrer / device / browser / os / country / UTM; parseUserAgent and geoFromHeaders build those props from a request. See docs/web-preset.md.

React

import { useMetric, useTop } from "@vllnt/convex-analytics/react";
import { api } from "../convex/_generated/api";

const signups = useMetric(api.analytics.metric, { name: "signup" });
const byPlan = useTop(api.analytics.top, { name: "signup", dimension: "plan" });

Thin reactive wrappers over the host's re-exported query refs (metric, top, timeseries, uniques). react + convex are optional peer deps; backend-only consumers pull no React.

Security

  • Auth-agnostic mount — the host gates access and passes opaque subjectRef / sessionRef in.
  • Tables are sandboxed; the host reaches them only through the exported functions.
  • REST endpoints require an x-api-key header, compared timing-safe.

See docs/architecture.md.

Testing

pnpm test
import analyticsTest from "@vllnt/convex-analytics/test";
import { convexTest } from "convex-test";

const t = convexTest(hostSchema, hostModules);
analyticsTest.register(t);

Documentation

Schema · Client SDK · REST API · Web Preset · Multi-Product · Architecture. For AI agents: llms.txt · llms-full.txt.

Contributing

See CONTRIBUTING.md.

Author

Built by bntvllnt · bntvllnt.com · X @bntvllnt

Part of the @vllnt Convex component fleet — vllnt.com

If this is useful, sponsor the work.

License

MIT — see LICENSE.