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

trakoo

v2.3.0

Published

A highly typed, provider-agnostic analytics library for TypeScript applications

Readme

trakoo

A typed, provider-agnostic analytics library for TypeScript applications. Define one event registry, create an application-owned client or server instance, and send the same events to any configured provider.

Read the full documentation for framework guides and provider-specific setup.

Agent Skill

Give your coding agent Trakoo-specific integration guidance for typed events, client/server boundaries, providers, and framework setup:

npx skills add multiplehats/trakoo --skill trakoo

The source is skills/trakoo/SKILL.md and follows the portable Agent Skills format used by skills.sh-compatible agents. See the Agent Skill docs for agent targeting, global installs, and manual setup.

Features

  • Event names and properties inferred from one registry
  • Validator-free TypeScript definitions with optional Standard Schema validation
  • Separate browser and server entry points
  • Typed user traits and per-event user context
  • Provider fan-out and routing
  • Fresh, independently configured analytics instances

Installation

pnpm add trakoo

Core trakoo has no provider SDK dependencies. Providers that need no SDK ship with it: Bento's browser provider, Pirsch, Visitors, and the Proxy. Providers that wrap a vendor SDK are separate @trakoo/* packages. Install the provider package with only the SDK for the side you use:

| Provider | Package | Browser SDK | Server SDK | |---|---|---|---| | PostHog | @trakoo/posthog | posthog-js | posthog-node | | OpenPanel | @trakoo/openpanel | @openpanel/web | @openpanel/sdk | | Bento (server) | @trakoo/bento | — | @bentonow/bento-node-sdk | | EmitKit | @trakoo/emitkit | — | @emitkit/js 3 |

# PostHog in the browser
pnpm add trakoo @trakoo/posthog posthog-js

# PostHog on the server
pnpm add trakoo @trakoo/posthog posthog-node

Quick start

1. Define events

The root trakoo entry point contains environment-neutral event helpers and shared types.

import { defineEvents, noProperties, typed } from 'trakoo';

export const appEvents = defineEvents({
  userSignedUp: {
    name: 'user_signed_up',
    category: 'user',
    properties: typed<{
      userId: string;
      email: string;
      plan: 'free' | 'pro' | 'enterprise';
      referralSource?: string;
    }>()
  },
  featureUsed: {
    name: 'feature_used',
    category: 'engagement',
    properties: typed<{
      featureName: string;
      duration?: number;
    }>()
  },
  sessionStarted: {
    name: 'session_started',
    category: 'user',
    properties: noProperties()
  }
});

typed<T>() gives you compile-time checking without a runtime validator. It verifies at runtime only that a property-bearing event receives a non-array object. Use noProperties() when callers must omit the properties argument entirely.

2. Create a client instance

Factories and providers come from environment-specific subpaths. Pass the registry as a value; no event generic is needed.

import { createClientAnalytics } from 'trakoo/client';
import { PostHogClientProvider } from '@trakoo/posthog/client';
import { appEvents } from './events';

export const analytics = createClientAnalytics({
  events: appEvents,
  providers: [
    new PostHogClientProvider({
      token: import.meta.env.VITE_POSTHOG_KEY,
      api_host: import.meta.env.VITE_POSTHOG_HOST
    })
  ],
  debug: import.meta.env.DEV
});

Each factory call returns a fresh instance. trakoo does not keep a global analytics singleton. Create and own the instance in your application, then import that owned instance where you track.

await analytics.track('user_signed_up', {
  userId: 'user-123',
  email: '[email protected]',
  plan: 'pro'
});

await analytics.track('session_started');

The registry drives autocomplete and rejects misspelled names, missing properties, extra properties, and a properties argument for session_started.

3. Create a server instance

import { createServerAnalytics } from 'trakoo/server';
import { PostHogServerProvider } from '@trakoo/posthog/server';
import { appEvents } from './events';

export const serverAnalytics = createServerAnalytics({
  events: appEvents,
  providers: [
    new PostHogServerProvider({
      apiKey: process.env.POSTHOG_API_KEY!,
      host: process.env.POSTHOG_HOST
    })
  ]
});

Server tracking accepts user and request context per call:

await serverAnalytics.track('feature_used', {
  featureName: 'export-data',
  duration: 1500
}, {
  userId: 'user-123',
  user: {
    email: '[email protected]',
    traits: { plan: 'pro' }
  },
  context: {
    page: { path: '/api/export' }
  }
});

await serverAnalytics.shutdown();

Call shutdown() before a serverless request or worker exits so providers can flush queued events.

Runtime validation with Standard Schema

Standard Schema is an interface implemented by validator libraries; it is not a validator runtime that trakoo requires. The primary API remains validator-free typed<T>(). When an event crosses an untrusted boundary, pass a compatible validator directly. Zod implements Standard Schema:

import { defineEvents } from 'trakoo';
import { z } from 'zod';

export const commerceEvents = defineEvents({
  orderCompleted: {
    name: 'order_completed',
    category: 'conversion',
    properties: z.object({
      orderId: z.string(),
      amount: z.coerce.number().positive()
    })
  }
});

The schema's input type controls what track() accepts. Its output type controls the validated and transformed properties passed to providers. In this example, amount may be a coercible input, but providers always receive a positive number.

Validation failures are dropped by default. For strict handling, opt into throwing and report the normalized, payload-free error:

const analytics = createClientAnalytics({
  events: commerceEvents,
  providers: [/* ... */],
  validation: {
    onFailure: 'throw',
    onError: (error) => reportValidationFailure(error)
  }
});

AnalyticsValidationError contains a code, event name, and normalized issue messages/paths. Validator issue messages are retained for onError and thrown errors, but the complete input payload is never attached to the error. With debug: true and no onError, the fallback warning deliberately omits issue messages and input values; it contains only the code, event name, and issue paths.

The error callback is awaited before the configured drop or throw policy is applied. Async schemas also mean concurrent track() calls can reach providers in validation-completion order rather than call order. Await calls sequentially if delivery order matters.

This validation policy applies only to event lookup and property validation. Initialization failures and provider failures keep their existing behavior.

Typed user traits

Use another typed<T>() marker for custom traits. The factory still infers the event registry from events.

import { typed } from 'trakoo';
import { createClientAnalytics } from 'trakoo/client';
import { appEvents } from './events';

interface UserTraits {
  email: string;
  name: string;
  plan: 'free' | 'pro' | 'enterprise';
  company?: string;
}

export const analytics = createClientAnalytics({
  events: appEvents,
  userTraits: typed<UserTraits>(),
  providers: [/* ... */]
});

analytics.identify('user-123', {
  email: '[email protected]',
  name: 'Ada Lovelace',
  plan: 'pro'
});

Client analytics remembers the identified user until reset(). Server analytics is stateless: pass userId and user with each track() call.

Multiple providers and routing

import { createClientAnalytics } from 'trakoo/client';
import {
  BentoClientProvider,
  VisitorsClientProvider
} from 'trakoo/providers/client';
import { PostHogClientProvider } from '@trakoo/posthog/client';

const analytics = createClientAnalytics({
  events: appEvents,
  providers: [
    new PostHogClientProvider({ token: 'posthog-token' }),
    {
      provider: new BentoClientProvider({ siteUuid: 'bento-site' }),
      methods: ['identify', 'track'],
      events: ['user_signed_up']
    },
    new VisitorsClientProvider({ token: 'visitors-token' })
  ]
});

Every configured provider receives eligible calls. Routing can restrict methods, exact event names, excluded events, or event-name patterns. Set pii: false on an entry to keep emails and names from that provider while the others still receive them.

Better Auth

@trakoo/better-auth is a Better Auth plugin that sends typed events for sign-ups, sign-ins, sessions, organizations, API keys, subscriptions, and more. Merge its authEvents into your registry and add trakooAuth({ analytics }) to your Better Auth plugins.

Custom providers

Extend the base class from the environment where the provider will run:

import {
  BaseAnalyticsProvider,
  type BaseEvent,
  type EventContext
} from 'trakoo/client';

export class ConsoleProvider extends BaseAnalyticsProvider {
  name = 'Console';

  initialize() {}
  identify(userId: string, traits?: Record<string, unknown>) {
    console.log('identify', { userId, traits });
  }
  track(event: BaseEvent, context?: EventContext) {
    console.log('track', { event, context });
  }
  pageView(properties?: Record<string, unknown>) {
    console.log('page view', properties);
  }
  reset() {}
}

See Creating Custom Providers for the full lifecycle.

Import map

| Import | Contents | |---|---| | trakoo | defineEvents, typed, noProperties, BaseAnalyticsProvider, validation error, and shared types | | trakoo/client | Client factory, browser analytics class, client-safe base provider exports | | trakoo/server | Server factory, server analytics class, server-safe base provider exports | | trakoo/providers/client | Browser providers without an SDK: Bento, Pirsch, Visitors, Proxy | | trakoo/providers/server | Server providers without an SDK: Pirsch, proxy ingestion helpers | | @trakoo/posthog/client, @trakoo/posthog/server | PostHog providers | | @trakoo/openpanel/client, @trakoo/openpanel/server | OpenPanel providers | | @trakoo/bento/server | Bento server provider | | @trakoo/emitkit/server | EmitKit server provider | | @trakoo/better-auth | Better Auth plugin and its authEvents registry |

Do not import factories from the root or use a combined provider entry point. Provider packages have no root entry; import from their /client or /server subpath.

API summary

defineEvents(definitions)

Creates the branded runtime registry required by both factories. Duplicate wire names throw while the registry is created.

typed<T>()

Declares an object-shaped compile-time input/output type without validating its fields at runtime.

noProperties()

Declares an event that must be tracked without a properties argument.

createClientAnalytics(config)

Requires config.events. Optional configuration includes providers, userTraits, validation, debug, and enabled. Returns a fresh client instance.

createServerAnalytics(config)

Requires config.events. Optional configuration includes providers, userTraits, validation, debug, enabled, and defaultContext. Returns a fresh server instance.

Best practices

  1. Keep a single authoritative registry and pass that registry value to every analytics factory.
  2. Prefer typed<T>(); add a Standard Schema validator only at boundaries that need runtime checking or transformation.
  3. Model zero-property events with noProperties().
  4. Create analytics and provider instances in application-owned modules; do not rely on hidden global state.
  5. Use client/server subpaths so environment-specific code stays out of the wrong bundle.
  6. Await server events and call shutdown() when delivery must complete before the runtime exits.
  7. Never send secrets or unnecessary personal data to analytics providers.

Learn more

Contributing

Contributions are welcome. Please open an issue or pull request with a focused description and tests where behavior changes.