From 1e29da45d847ec29b76ad9b9faefcaaa4ae69ea9 Mon Sep 17 00:00:00 2001 From: GJ Date: Sun, 19 Apr 2026 10:38:25 +0200 Subject: [PATCH] =?UTF-8?q?feat!:=20rename=20trackDocView=20=E2=86=92=20tr?= =?UTF-8?q?ackVisit=20(and=20TrackDocViewOptions)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function name implied this library only makes sense for docs sites, but the loose-detection tier in v0.2.0 plus consumer interest in marketing pages made clear that the "doc view" framing is too narrow. Rename the function and its options type; the `doc_view` event name itself stays the default because it's configurable via `eventName` — backward-compat for any existing PostHog dashboards that filter on it. BREAKING: import `trackVisit` instead of `trackDocView`, and `TrackVisitOptions` instead of `TrackDocViewOptions`. Since no production deployment depends on v0.2.0 yet, landing this as 0.3.0 rather than deprecating-then-removing at 1.0. Migration is a global find-and-replace at consumer sites. - src/track.ts: function + options type renamed - src/index.ts, src/types.ts, src/bots.ts docstring: updated references - test/track.test.ts: describe block + all 8 invocations renamed - README.md: quick start + how-it-works + FAQ updated - package.json: 0.2.0 → 0.3.0 All 47 tests green, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 12 ++++++------ package.json | 2 +- src/bots.ts | 2 +- src/index.ts | 4 ++-- src/track.ts | 6 +++--- src/types.ts | 2 +- test/track.test.ts | 22 +++++++++++----------- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 80c3af4..143da28 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,12 @@ Server logs have the data, but turning them into analytics is a pipeline project ## What you get ```ts -import { trackDocView, posthogAnalytics } from '@apideck/agent-analytics' +import { trackVisit, posthogAnalytics } from '@apideck/agent-analytics' const analytics = posthogAnalytics({ apiKey: process.env.POSTHOG_KEY! }) export function middleware(req: NextRequest) { - void trackDocView(req, { analytics }) // ← that's the whole thing + void trackVisit(req, { analytics }) // ← that's the whole thing return NextResponse.next() } ``` @@ -121,11 +121,11 @@ Ships with **PostHog**, **webhook**, and **custom** adapters. BYO analytics. ```ts // middleware.ts import { - trackDocView + trackVisit } from '@apideck/agent-analytics' export function middleware(req) { - void trackDocView(req, { + void trackVisit(req, { analytics, source: 'page-view' }) @@ -366,14 +366,14 @@ Full middleware example: [`README.md → Markdown mirror helpers`](./README.md#m
Will this slow down my middleware? -No. `trackDocView` returns a promise you don't await, and the underlying `fetch` uses `keepalive: true` — the browser / runtime guarantees the request completes after your response returns. Your critical path is: `req.headers.get('user-agent')` + a regex test + a `void fetch(...)`. Sub-millisecond. +No. `trackVisit` returns a promise you don't await, and the underlying `fetch` uses `keepalive: true` — the browser / runtime guarantees the request completes after your response returns. Your critical path is: `req.headers.get('user-agent')` + a regex test + a `void fetch(...)`. Sub-millisecond.
What if my analytics backend is down? -The adapter call is wrapped in try/catch — `trackDocView` never throws, even if PostHog / your webhook / your custom callback crashes. You lose the event, not the response. +The adapter call is wrapped in try/catch — `trackVisit` never throws, even if PostHog / your webhook / your custom callback crashes. You lose the event, not the response.
diff --git a/package.json b/package.json index 2ab7319..29ed055 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apideck/agent-analytics", - "version": "0.2.0", + "version": "0.3.0", "description": "Track AI agent and bot traffic to your Next.js / Vercel app — PostHog, webhooks, or any custom analytics backend. Detects Claude, ChatGPT, Perplexity, Google-Extended, and more.", "keywords": [ "ai", diff --git a/src/bots.ts b/src/bots.ts index e4d700b..1ff14d7 100644 --- a/src/bots.ts +++ b/src/bots.ts @@ -150,7 +150,7 @@ export interface AgentClassification { /** * One-stop classification of a user-agent. Combines {@link isAiBot}, * {@link isHttpClient}, and {@link parseBotName} into a single structured - * result. Used internally by `trackDocView` to populate event properties; + * result. Used internally by `trackVisit` to populate event properties; * useful in consumer code when you need all signals at once. */ export function classifyAgent(userAgent: string | null | undefined): AgentClassification { diff --git a/src/index.ts b/src/index.ts index 950709b..7462af7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { trackDocView } from './track.js' +export { trackVisit } from './track.js' export { AI_BOT_PATTERN, HTTP_CLIENT_PATTERN, @@ -16,5 +16,5 @@ export { customAnalytics } from './adapters/custom.js' export type { AnalyticsAdapter, CaptureEvent, - TrackDocViewOptions + TrackVisitOptions } from './types.js' diff --git a/src/track.ts b/src/track.ts index 6a30e4f..962d01f 100644 --- a/src/track.ts +++ b/src/track.ts @@ -1,6 +1,6 @@ import { classifyAgent, isAiBot } from './bots.js' import { hashId } from './hash.js' -import type { TrackDocViewOptions } from './types.js' +import type { TrackVisitOptions } from './types.js' /** * Capture an event describing the incoming request. Fire-and-forget: awaits @@ -10,9 +10,9 @@ import type { TrackDocViewOptions } from './types.js' * When `onlyBots` is true (the default), skips capture unless the UA matches * {@link AI_BOT_PATTERN}. Set `onlyBots: false` to track every visit. */ -export async function trackDocView( +export async function trackVisit( req: Request, - opts: TrackDocViewOptions + opts: TrackVisitOptions ): Promise { const userAgent = req.headers.get('user-agent') || '' diff --git a/src/types.ts b/src/types.ts index 261bf8c..f4a9028 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,7 @@ export interface AnalyticsAdapter { capture(event: CaptureEvent): Promise | void } -export interface TrackDocViewOptions { +export interface TrackVisitOptions { analytics: AnalyticsAdapter /** * Label describing how the request arrived (e.g. `'page-view'`, `'md-suffix'`, diff --git a/test/track.test.ts b/test/track.test.ts index 0d2b201..77dc262 100644 --- a/test/track.test.ts +++ b/test/track.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { customAnalytics } from '../src/adapters/custom.js' -import { trackDocView } from '../src/track.js' +import { trackVisit } from '../src/track.js' import type { CaptureEvent } from '../src/types.js' function makeRequest( @@ -10,14 +10,14 @@ function makeRequest( return new Request(url, { headers }) } -describe('trackDocView', () => { +describe('trackVisit', () => { it('captures when the UA is a known AI bot', async () => { const captured: CaptureEvent[] = [] const analytics = customAnalytics((e) => { captured.push(e) }) - await trackDocView( + await trackVisit( makeRequest('https://example.com/docs/intro', { 'user-agent': 'ClaudeBot/1.0', 'x-forwarded-for': '1.2.3.4', @@ -46,7 +46,7 @@ describe('trackDocView', () => { it('sets bot_name to Browser for human traffic when onlyBots is false', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'Mozilla/5.0 (Macintosh) Chrome/120' }), @@ -61,7 +61,7 @@ describe('trackDocView', () => { it('sets coding_agent_hint and ua_category for HTTP-library UAs (onlyBots: false)', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/docs/intro', { 'user-agent': 'curl/8.4.0' }), { analytics: customAnalytics(spy), onlyBots: false } ) @@ -76,7 +76,7 @@ describe('trackDocView', () => { it('skips capture when UA is not a bot and onlyBots is on (default)', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'Mozilla/5.0 (Macintosh) Chrome/120' }), @@ -87,7 +87,7 @@ describe('trackDocView', () => { it('captures every request when onlyBots is false', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'Mozilla/5.0 (Macintosh) Chrome/120' }), @@ -100,7 +100,7 @@ describe('trackDocView', () => { it('honours a custom event name', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'ClaudeBot/1.0' }), { analytics: customAnalytics(spy), eventName: 'agent_fetch' } ) @@ -113,7 +113,7 @@ describe('trackDocView', () => { throw new Error('downstream offline') }) await expect( - trackDocView( + trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'ClaudeBot' }), { analytics } ) @@ -122,7 +122,7 @@ describe('trackDocView', () => { it('uses the first x-forwarded-for value when multiple are present', async () => { const spy = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'ClaudeBot', 'x-forwarded-for': '203.0.113.1, 10.0.0.1' @@ -133,7 +133,7 @@ describe('trackDocView', () => { const b = ( await (async () => { const spy2 = vi.fn() - await trackDocView( + await trackVisit( makeRequest('https://example.com/page', { 'user-agent': 'ClaudeBot', 'x-forwarded-for': '203.0.113.1, 10.0.0.2'