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

@gyne-antpack/tracking-sdk

v1.2.1

Published

Enterprise behavioral tracking SDK for web and Node.js

Readme

@gyne/tracking-sdk

Enterprise behavioral tracking SDK for web and Node.js. Similar to Segment/RudderStack but fully owned and private.

Installation

# npm
npm install @gyne/tracking-sdk

# pnpm
pnpm add @gyne/tracking-sdk

# yarn
yarn add @gyne/tracking-sdk

Configure your .npmrc to pull from GitHub Packages:

@gyne:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN

Quick Start

Vanilla JS / Browser

import { createClient } from "@gyne/tracking-sdk";

const client = await createClient({
  writeKey: "wk_your_key_here",
  autoSessionTracking: true,
});

// Track an event
await client.track("button_click", { label: "Hero CTA" });

// Identify a user
await client.identify("user_123", {
  email: "[email protected]",
  plan: "pro",
});

// Track a page view
await client.page("Home Page");

Next.js App Router

app/layout.tsx

import { GyneScript } from "@gyne/tracking-sdk/nextjs";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <GyneScript config={{ writeKey: process.env.NEXT_PUBLIC_GYNE_WRITE_KEY }}>
          {children}
        </GyneScript>
      </body>
    </html>
  );
}

components/MyButton.tsx

"use client";
import { useTrack } from "@gyne/tracking-sdk/react";

export function MyButton() {
  const track = useTrack();
  return (
    <button onClick={() => track("button_click", { label: "Buy Now" })}>
      Buy Now
    </button>
  );
}

Server Components / API Routes

import { createServerClient } from "@gyne/tracking-sdk/nextjs";

const analytics = createServerClient({ writeKey: process.env.GYNE_WRITE_KEY });

// In a Server Action
export async function serverAction() {
  await analytics.init();
  await analytics.track("server_event", { source: "checkout" });
}

API Reference

createClient(config) → Promise<GyneClient>

Creates and initializes a client. Subsequent calls with the same tenantId return the existing instance.

client.track(event, properties?)

await client.track("purchase", {
  orderId: "ORD-001",
  total: 99.99,
  currency: "USD",
});

client.identify(userId, traits?)

await client.identify("usr_123", {
  email: "[email protected]",
  name: "Jane Doe",
  plan: "enterprise",
});

client.page(name?, properties?)

await client.page("Checkout", {
  url: "https://shop.acme.com/checkout",
  referrer: "https://shop.acme.com/cart",
});

client.reset()

Clears identity, creates a new anonymous ID, and flushes the queue.

client.flush()

Force-flushes the event queue immediately.


Configuration

| Option | Type | Default | Description | |---|---|---|---| | writeKey | string | required | API key | | apiUrl | string | https://api.gyne.io/v1/batch | Collector endpoint | | tenantId | string | "" | Multi-tenant identifier | | debug | boolean | false | Enable console logging | | flushAt | number | 20 | Events before auto-flush | | flushInterval | number | 5000 | ms between flushes | | maxBatchSize | number | 100 | Max events per batch | | maxQueueSize | number | 1000 | Max queued events | | maxRetries | number | 3 | Retry limit per event | | retryBaseDelay | number | 1000 | Exponential backoff base (ms) | | requireConsent | boolean | false | Gate events on consent | | autoPageView | boolean | false | Auto-track page views | | autoSessionTracking | boolean | true | Auto session start/end | | sessionTimeout | number | 1800000 | Inactivity timeout (ms) | | cookieDomain | string | "" | Cross-subdomain cookies | | isServer | boolean | auto | Disable browser APIs | | plugins | Plugin[] | [] | Plugin array | | middlewares | MiddlewareFn[] | [] | Middleware array | | transport | Transport | HTTP+Beacon | Custom transport |


Consent Management (GDPR)

const client = await createClient({
  writeKey: "wk_...",
  requireConsent: true,
  consent: {
    categories: {
      analytics: false,
      marketing: false,
      functional: true,  // always on
      performance: false,
    },
  },
});

// Later, when user accepts:
client.consent().grant(["analytics", "marketing"]);

// OneTrust integration example:
window.OneTrust?.OnConsentChanged(() => {
  const groups = window.OnetrustActiveGroups?.split(",") ?? [];
  if (groups.includes("C0002")) client.consent().grant("analytics");
});

Plugin System

import type { Plugin, PluginContext } from "@gyne/tracking-sdk";

const myPlugin: Plugin = {
  name: "MyPlugin",
  version: "1.0",

  initialize(config, anonymousId) {
    console.log("Plugin initialized with writeKey:", config.writeKey, "anonymousId:", anonymousId);
  },

  before(ctx: PluginContext): PluginContext {
    // Enrich or transform the event before it's sent
    return {
      ...ctx,
      event: {
        ...ctx.event,
        properties: {
          ...(ctx.event.type === "track" ? ctx.event.properties : {}),
          app_version: "2.1.0",
        },
      },
    };
  },

  after(ctx: PluginContext): PluginContext {
    // React to events after they're queued (e.g., forward to 3rd party)
    return ctx;
  },

  onConsent(consent) {
    console.log("Consent updated:", consent);
  },
};

const client = await createClient({
  writeKey: "wk_...",
  plugins: [myPlugin],
});

Autocapture

AutocapturePlugin automatically captures user interactions — clicks, form submissions, and input changes — without any manual instrumentation. Each interaction fires a $autocapture track event with full element metadata.

import { createClient, createAutocapturePlugin } from "@gyne/tracking-sdk";

const client = await createClient({
  writeKey: "wk_your_key_here",
  plugins: [
    createAutocapturePlugin({
      capture: ["click", "submit", "change"],
      maskInputs: true,          // never capture input values (default)
      blockSelector: ".private, [data-gyne-ignore]",
      captureAttributes: ["data-product-id", "aria-label"],
    }),
  ],
});
// From this point on, all clicks on links, buttons, and form submissions
// are tracked automatically — no client.track() calls needed.

Options

| Option | Type | Default | Description | |---|---|---|---| | capture | Array<"click"\|"submit"\|"change"> | ["click","submit","change"] | DOM event types to listen for | | maskInputs | boolean | true | Replace input values with [masked] (recommended for GDPR) | | blockSelector | string | "[data-gyne-ignore]" | CSS selector — elements matching this are never captured | | maxTextLength | number | 100 | Truncate element text content to this length | | captureAttributes | string[] | [] | Extra HTML attributes to include (e.g. data-*, aria-*) |

Emitted properties

Every $autocapture event includes:

| Property | Example | |---|---| | $event_type | "click" | | $el_tag | "button" | | $el_id | "hero-cta" | | $el_classes | ["btn-primary","btn-lg"] | | $el_text | "Buy Now" | | $el_href | "https://..." (anchors only) | | $el_name | "email" (inputs only) | | $el_type | "submit" (inputs only) | | $el_value | "[masked]" or actual value | | $css_selector | "main > section > button#hero-cta" | | $attr_data-product-id | "prod_42" (custom attributes) |

Opting out individual elements

Add data-gyne-ignore to any element (or a container) to exclude it from autocapture:

<div data-gyne-ignore>
  <!-- nothing inside here will be captured -->
  <input type="text" placeholder="Private field" />
  <button>Internal action</button>
</div>

GDPR considerations

  • maskInputs: true (default) ensures no user-typed data ever leaves the browser
  • Password fields are always masked regardless of maskInputs
  • Combine with requireConsent: true to hold all events until the user accepts analytics cookies
  • Use blockSelector to exclude sensitive sections of your UI

Segmentation

The SDK can automatically fetch the behavioral segment of the current user and cache it locally, enabling instant personalization without repeated API calls.

How it works

On every page load, the SDK calls the segmentation endpoint with the user's anonymousId. The resolved segment is stored in localStorage under the key gyne_user_segment. Subsequent reads are synchronous and instant — no network involved.

page load → fetch segment (background) → localStorage["gyne_user_segment"] = "interesado"

Setup

Add createSegmentationPlugin() to the plugins array when initializing the client:

import { createClient, createSegmentationPlugin } from '@gyne-antpack/tracking-sdk';

const client = await createClient({
  writeKey: 'wk_...',
  plugins: [createSegmentationPlugin()],
});

Reading the segment

getUserSegment() reads the cached value synchronously. It does not call the API.

import { getUserSegment } from '@gyne-antpack/tracking-sdk';

const segment = getUserSegment(); // "interesado" | "explorador" | null

Returns null if the segment hasn't been fetched yet (e.g., first visit before the background call resolves).

Personalization example

const segment = getUserSegment() ?? 'anonymous_user';

if (segment === 'interesado') {
  showPricingBanner();
} else if (segment === 'explorador') {
  showWelcomeTour();
}

Options

createSegmentationPlugin({
  // Override the endpoint URL (useful for self-hosted APIs)
  segmentationUrl: 'https://your-api.com/v1/segment-lookup',

  // Value to store when the lookup fails. Default: "anonymous_user"
  defaultSegment: 'anonymous_user',
})

You can also set these globally in createClient config:

| Option | Type | Default | Description | |---|---|---|---| | segmentationUrl | string | derived from apiUrl | Override segmentation endpoint | | segmentDefaultValue | string \| null | "anonymous_user" | Fallback when lookup fails |

Storage

| Key | Value | Description | |---|---|---| | gyne_user_segment | "interesado" | Current segment key | | gyne_user_segment_meta | JSON | Response time, success flag, timestamp |

Performance notes

  • The API is called once per full page load — never on SPA route changes.
  • The fetch runs asynchronously in the background and never blocks rendering.
  • Reads via getUserSegment() are synchronous and instantaneous (localStorage).
  • The writeKey is sent as an internal header — it is never exposed to user-facing code.

A/B Experiments

The SDK can resolve which variant of an experiment the current user should see, and automatically tracks the exposure event.

How it works

client.experiment("key") → GET /v1/experiment-lookup (anonymous_id, experiment_key)
                          → sessionStorage["gyne_exp_key"] = result   (cached for the tab session)
                          → auto-tracks "experiment_viewed"
                          → returns { key: "A" | "B", experimentId, content } or null
  • Resolution happens live on every call — there's no propagation delay between activating an experiment in the dashboard and it being picked up by the SDK.
  • Within the same browser session, the same user keeps the same variant (cached in sessionStorage), so client.experiment() is safe to call multiple times without re-bucketing the user.
  • Returns null when there's no active experiment for that key, the SDK is running server-side, or the user doesn't belong to the experiment's target segment (if one is configured).
  • content is a free-form Record<string, string> — whatever fields were set on the variant when the experiment was created (e.g. headline, cta, body).

Usage

The SDK does not auto-inject anything into the DOM — you fetch the variant and apply it yourself.

const result = await client.experiment("hero-cta-julio-2026");

if (result) {
  document.querySelector("#hero-headline")!.textContent = result.content.headline;
  document.querySelector("#hero-cta")!.textContent = result.content.cta;
  document.querySelector("#hero-body")!.textContent = result.content.body;
}
// If result is null, leave the page's default content as-is.

React example:

const [variant, setVariant] = useState<ExperimentResult | null>(null);

useEffect(() => {
  client.experiment("hero-cta-julio-2026").then(setVariant);
}, []);

return <h1>{variant?.content.headline ?? "Default headline"}</h1>;

Tracking the goal event

Exposure (experiment_viewed) is tracked automatically inside client.experiment(). The conversion/goal event is not automatic — fire it yourself with client.track() using the same event name configured as the experiment's goal_event (e.g. purchase, form_completed):

await client.track("purchase", { orderId: "ORD-001", total: 99.99 });

API

client.experiment(experimentKey: string): Promise<ExperimentResult | null>

interface ExperimentResult {
  key: "A" | "B";
  experimentId: string;
  content: Record<string, string>;
}

Middleware

Middleware receives the event and a next function. Call next(event) to continue, or call next(null) / omit the call to drop the event.

const client = await createClient({
  writeKey: "wk_...",
  middlewares: [
    // Enrich every event with app version
    (event, next) => {
      next({ ...event, properties: { ...event.properties, app_version: "2.0" } });
    },

    // Drop events from internal users
    (event, next) => {
      if (event.userId?.startsWith("internal_")) return; // drop
      next(event);
    },
  ],
});

Multi-Tenant

// Tenant A
const clientA = await createClient({ writeKey: "wk_a", tenantId: "tenant_a" });

// Tenant B — separate queue, identity, and session
const clientB = await createClient({ writeKey: "wk_b", tenantId: "tenant_b" });

// Retrieve a client by tenantId elsewhere
import { getClient } from "@gyne/tracking-sdk";
const clientA = getClient("tenant_a");

Event Queue & Reliability

The SDK guarantees at-least-once delivery:

  • Events are persisted to localStorage immediately on enqueue
  • On page unload, unflushed events are sent via the Beacon API
  • Failed batches are retried with exponential backoff + jitter
  • On page reload, persisted events resume from where they left off
  • When the queue is full, oldest pending events are dropped (backpressure)

Handling millions of events

For high-volume sites:

  1. Deploy a collector proxy (e.g., a Vercel Edge Function at /api/gyne/batch) to batch before forwarding to Gyne servers
  2. Set flushAt: 50 and maxBatchSize: 200 to reduce HTTP round-trips
  3. Use requireConsent: true to avoid sending events for anonymous/opted-out users
  4. Enable the PiiScrubberPlugin to strip PII before it leaves the browser

Security

  • The writeKey is a write-only key — it cannot read data from the API
  • Use HTTPS exclusively (Secure flag on identity cookies)
  • The PiiScrubberPlugin strips emails, credit cards, SSNs, and phone numbers
  • Set cookieDomain to restrict cookies to your domain
  • SameSite=Lax cookies by default

Tree Shaking

The SDK is fully tree-shakeable. Import only what you need:

// Only ships the core client — no React adapter
import { createClient } from "@gyne/tracking-sdk";

// Only ships React hooks — no Node.js transport
import { useTrack } from "@gyne/tracking-sdk/react";