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

@grainql/tag

v4.4.6

Published

Grain Tag - Lightweight analytics SDK for Grain

Downloads

177

Readme


Installation

Script Tag (via Cloudflare Workers)

<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>

The SDK auto-initializes with your tenant config injected at the edge. No setup needed.

npm

npm install @grainql/tag

Quick Start

Script Tag

<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>
<script>
  // Auto-initialized — start tracking immediately
  GrainTag.track('button_clicked', { button: 'signup' });

  // Identify a user after login
  GrainTag.identify('user-123');
</script>

npm (React, Next.js, Vue, Svelte, etc.)

import { init, track, identify } from '@grainql/tag';

// Initialize once
init({ tenantId: 'your-tenant-id' });

// Track events — even before init(), events are queued and replayed
track('page_viewed', { page: '/home' });

// Identify after login
identify('user-123');

SSR / Server-Side

import { init, track } from '@grainql/tag';

// Safe to call on the server — returns a no-op stub, never crashes
const grain = init({ tenantId: 'your-tenant-id' });
track('page_viewed'); // silently ignored on server, works on client

Configuration

init({
  tenantId: 'your-tenant-id',        // Required
  apiUrl: 'https://clientapis.grainql.com', // Default
  debug: false,                       // Enable debug logging
  consentMode: 'auto',               // 'auto' | 'opt-in' | 'opt-out'
  enablePageViews: true,             // Automatic page view tracking
  enableHeatmaps: true,              // Click and scroll heatmaps
  enableSnapshots: true,             // DOM snapshots for visual overlays
  enableRescue: true,                // Exit-intent rescue popups
  batchSize: 50,                     // Events per batch
  flushInterval: 5000,               // Flush interval in ms
  retryAttempts: 3,                  // Retry attempts on failure
});

API Reference

Event Tracking

track(eventName, properties?)        // Track a custom event

Identity

identify(userId)                     // Set user identity

Consent

grain.consent.grant(['analytics'])   // Grant consent for categories
grain.consent.revoke()               // Revoke consent
grain.consent.status()               // Get current consent state

Lifecycle

getInstance()                        // Get the current SDK instance
isInitialized()                      // Check if SDK is initialized
destroy()                            // Tear down and allow re-init
grain.flush()                        // Flush pending events
grain.isReady()                      // True if running in browser

Consent Modes

| Mode | Default Behavior | On Consent | |------|-----------------|------------| | auto | Cookieless (daily rotating IDs) | Upgrades to permanent IDs | | opt-in | Cookieless until explicit consent | Upgrades to permanent IDs | | opt-out | Permanent IDs by default | Downgrades to cookieless on revoke |

How It Works

Events flow through a simple pipeline:

  1. track() enqueues events into an in-memory batch — it never blocks.
  2. Every 5 seconds (or when the batch reaches 50 events), events are flushed to the Grain API.
  3. Failed requests are retried with exponential backoff up to 3 times.
  4. On page unload or tab switch, remaining events are delivered via the Beacon API for reliable last-mile delivery.
  5. User identity uses daily rotating IDs by default (cookieless). On consent, it upgrades to persistent IDs stored in localStorage.

The SDK automatically attaches device, browser, os, UTM parameters, first-touch attribution, and session data to every event.

Building from Source

npm run build:sdk       # Build SDK (IIFE, ESM, CJS + type declarations)
npm run build:worker    # Build Cloudflare Worker
npm run build           # Build everything
npm run build:dev       # Development build (unminified)
npm run typecheck       # TypeScript type checking

Grain SDKs

| Use Case | SDK | |----------|-----| | Browser analytics, heatmaps, snapshots | @grainql/tag (this package) | | Script tag delivery (Cloudflare Workers) | @grainql/tag (this package) | | Remote configuration | @grainql/analytics-web | | Server-side event tracking (Node.js) | @grainql/analytics-web |

Migrating from @grainql/analytics-web

If you were using @grainql/analytics-web for browser analytics:

// Before
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({ tenantId: '...' });
grain.track('event');

// After
import { init, track } from '@grainql/tag';
init({ tenantId: '...' });
track('event');
  • createGrainAnalytics()init()
  • grain.setUserId()identify()
  • Consent API: grain.grantConsent()grain.consent.grant()

Contributing

See CONTRIBUTING.md.

License

MIT