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

@neowhale/telemetry

v1.1.4

Published

Error tracking, analytics, and Web Vitals for WhaleTools stores

Readme

Zero dependencies. Works in the browser and on the server.

Install

npm install @neowhale/telemetry

Peer dependencies: react >=18 (optional -- only needed for @neowhale/telemetry/react).

Quick Start

Browser (React)

import { WhaleTelemetry, WhaleErrorBoundary } from '@neowhale/telemetry/react'

export default function App() {
  return (
    <WhaleTelemetry apiKey="wk_live_..." storeId="your-store-uuid">
      <WhaleErrorBoundary fallback={<p>Something went wrong.</p>}>
        <YourApp />
      </WhaleErrorBoundary>
    </WhaleTelemetry>
  )
}

WhaleTelemetry calls init() on mount and destroy() on unmount. In the browser it automatically captures:

  • Unhandled errors and promise rejections
  • Page views (initial load and SPA navigation)
  • Web Vitals (LCP, FID, CLS, FCP)
  • Breadcrumbs (clicks, navigation, console warnings/errors, fetch)

Browser (Vanilla)

import { whaletools } from '@neowhale/telemetry'

whaletools.init({ apiKey: 'wk_live_...', storeId: 'your-store-uuid' })

whaletools.track('add_to_cart', { product_id: 'abc', price: 29.99 })
whaletools.captureError(new Error('Payment failed'))

Server (Next.js App Router)

import { withTelemetry } from '@neowhale/telemetry/server'

const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }

export const GET = withTelemetry(async (req) => {
  return Response.json({ ok: true })
}, config)

Server (Manual Error Reporting)

import { reportServerError } from '@neowhale/telemetry/server'

const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }

try {
  await riskyOperation()
} catch (err) {
  await reportServerError(err as Error, config, { context: 'cron_job' })
}

Server (AI/LLM Call Tracking)

import { reportAICall } from '@neowhale/telemetry/server'

const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }

const start = Date.now()
const response = await openai.chat.completions.create({ model: 'gpt-4o', messages })

await reportAICall({
  model: 'gpt-4o',
  provider: 'openai',
  prompt_tokens: response.usage?.prompt_tokens,
  completion_tokens: response.usage?.completion_tokens,
  duration_ms: Date.now() - start,
  status: 'ok',
}, config)

Configuration

WhaleToolsConfig (browser)

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | required | API key (wk_live_... or wk_test_...) | | storeId | string | required | Store UUID | | endpoint | string | "https://whale-gateway.fly.dev" | Gateway URL | | errors | boolean | true | Auto-capture unhandled errors | | analytics | boolean | true | Auto-capture page views | | vitals | boolean | true | Collect Web Vitals | | breadcrumbs | boolean | true | Auto-collect breadcrumbs | | environment | string | "production" | Environment tag | | serviceName | string | "store_client" | Service name tag | | serviceVersion | string | "" | Service version tag | | flushInterval | number | 5000 | Flush interval in ms | | flushThreshold | number | 10 | Max queued items before auto-flush | | debug | boolean | false | Log telemetry to console | | sampleRate | number | 1 | Analytics sample rate (0--1) | | beforeSend | (error: ErrorPayload) => ErrorPayload \| false | -- | Mutate or drop errors before send |

ServerConfig (server)

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | required | API key | | storeId | string | required | Store UUID | | endpoint | string | "https://whale-gateway.fly.dev" | Gateway URL | | serviceName | string | "store_server" | Service name tag | | environment | string | "production" | Environment tag |


API Reference

Core -- @neowhale/telemetry

The default export provides a pre-initialized whaletools singleton of type WhaleToolsClient.

import { whaletools } from '@neowhale/telemetry'

whaletools.init(config: WhaleToolsConfig): void

Initialize the client. Must be called once before other methods. In the browser, automatically installs error handlers, page view tracking, Web Vitals collection, and breadcrumb capture based on config flags.

whaletools.captureError(error: Error | string, extra?: Record<string, unknown>): void

Manually capture an error. Generates a SHA-256 fingerprint, attaches breadcrumbs, and queues for delivery.

whaletools.captureMessage(message: string, severity?: Severity, extra?: Record<string, unknown>): void

Capture a message as an error event. Severity is one of "debug" | "info" | "warning" | "error" | "fatal" (default "info").

whaletools.track(eventName: string, properties?: Record<string, unknown>): void

Track a custom analytics event. Automatically enriched with page_url and page_title. Subject to sampleRate.

whaletools.page(properties?: Record<string, unknown>): void

Track a page view. Enriched with page_url, page_path, and page_title. Called automatically on init and SPA navigation when analytics is enabled.

whaletools.identify(userId: string, traits?: Record<string, unknown>): void

Set user context. Traits such as email and name are attached to subsequent telemetry payloads.

whaletools.trackAICall(call: AICallPayload): void

Track an AI/LLM API call. Automatically calculates total_tokens from prompt_tokens + completion_tokens if omitted.

whaletools.addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void

Add a manual breadcrumb to the ring buffer (max 25).

whaletools.flush(): void

Force-flush all queued errors, events, vitals, and AI calls to the gateway.

whaletools.destroy(): void

Stop the flush timer, perform a final flush, and reset the initialized state.

addBreadcrumb(category, message, level?, data?) (standalone)

import { addBreadcrumb } from '@neowhale/telemetry'

addBreadcrumb('checkout', 'User entered payment info', 'info', { step: 3 })

Add a breadcrumb directly to the global ring buffer without going through the client instance.


React -- @neowhale/telemetry/react

import { WhaleTelemetry, WhaleErrorBoundary } from '@neowhale/telemetry/react'

<WhaleTelemetry {...config} />

Provider component. Accepts all WhaleToolsConfig props plus children. Calls whaletools.init() on mount and whaletools.destroy() on unmount.

<WhaleErrorBoundary />

React error boundary that reports caught render errors to WhaleTools.

| Prop | Type | Description | |---|---|---| | children | ReactNode | Child components to wrap | | fallback | ReactNode \| (error: Error) => ReactNode | Fallback UI on error | | onError | (error: Error, errorInfo: { componentStack: string }) => void | Callback on error |


Server -- @neowhale/telemetry/server

import { withTelemetry, reportServerError, reportAICall, reportAICallBatch } from '@neowhale/telemetry/server'

withTelemetry(handler, config): handler

Wraps a route handler (Next.js App Router, etc.) with automatic error capture. Catches unhandled errors, reports them to the gateway, and re-throws so the framework can handle the response.

reportServerError(error: Error, config: ServerConfig, extra?: Record<string, unknown>): Promise<void>

Manually report a server-side error. Use in catch blocks where withTelemetry is not applicable.

reportAICall(call: AICallReport, config: ServerConfig): Promise<void>

Report a single AI/LLM API call. total_tokens is auto-calculated from prompt_tokens + completion_tokens if omitted.

reportAICallBatch(calls: AICallReport[], config: ServerConfig): Promise<void>

Report multiple AI/LLM calls in a single request.


Types

ErrorPayload

interface ErrorPayload {
  error_type: string
  error_message: string
  stack_trace: string
  fingerprint: string
  severity: 'debug' | 'info' | 'warning' | 'error' | 'fatal'
  source_file: string
  source_line: number
  source_function: string
  tags: Record<string, string>
  extra: Record<string, unknown>
  breadcrumbs: Breadcrumb[]
  occurred_at: string
  platform: string
}

Breadcrumb

interface Breadcrumb {
  timestamp: string
  category: string
  message: string
  level: 'debug' | 'info' | 'warning' | 'error'
  data?: Record<string, unknown>
}

AnalyticsEvent

interface AnalyticsEvent {
  event_name: string
  properties: Record<string, unknown>
  timestamp: string
}

WebVital

interface WebVital {
  name: 'CLS' | 'FID' | 'LCP' | 'INP' | 'TTFB' | 'FCP'
  value: number
  rating: 'good' | 'needs-improvement' | 'poor'
  timestamp: string
}

AICallPayload

interface AICallPayload {
  model: string               // required
  provider?: string
  operation?: string
  prompt_tokens?: number
  completion_tokens?: number
  total_tokens?: number
  cost?: number
  duration_ms?: number
  status?: 'ok' | 'error'
  error_message?: string
  http_status?: number
  agent_id?: string
  conversation_id?: string
  tool_name?: string
  parent_span_id?: string
  attributes?: Record<string, unknown>
  timestamp?: string
}

Internals

Transport -- Telemetry is batched and sent via HTTP POST to /v1/stores/{storeId}/telemetry/ingest. On page unload, the client falls back to navigator.sendBeacon to avoid data loss.

Sessions -- visitor_id is persisted in localStorage (wt_vid). session_id (wt_sid) resets after 30 minutes of inactivity.

Fingerprinting -- Errors are deduplicated using a SHA-256 hash of type|message|source. Falls back to djb2 when SubtleCrypto is unavailable.

Breadcrumbs -- Automatically collected categories: ui.click, navigation, console.warn, console.error, fetch. Stored in a ring buffer (max 25 entries) and attached to error payloads.

Web Vitals -- LCP, FID, CLS, and FCP are measured via PerformanceObserver. Ratings follow web.dev thresholds.

License

MIT