@molecule/api-analytics-http
Provider bond · analytics · API (Node) · v1.0.1 · Apache-2.0
Generic HTTP analytics provider — POSTs track/identify/page events to a configured first-party endpoint (built for mlcl/molecule-mcp telemetry).
npm install @molecule/api-analytics-httpnpm · Source on GitHub · Implements @molecule/api-analytics
How it works
@molecule/api-analytics-http is a provider bond on the API (Node) side: it implements the analytics core interface (@molecule/api-analytics) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider } from '@molecule/api-analytics'
import { createHttpAnalyticsProvider } from '@molecule/api-analytics-http'
setProvider(
createHttpAnalyticsProvider({
url: 'https://api.molecule.dev/v1/telemetry/cli',
// Optional request tagging — defaults to 'unknown'.
source: 'mlcl',
}),
)
// track() now POSTs; failures are swallowed.Reference
Generic HTTP analytics provider.
POSTs each track() / identify() / page() call as a small JSON document
to a configured first-party endpoint (the url option or the
MOLECULE_ANALYTICS_URL env var). No endpoint is assumed — when none is
configured the provider no-ops, so an unconfigured consumer never silently
phones home. Every POST is best-effort: network failures are swallowed
(logged only via the optional onError hook), so telemetry can never break
the CLI or service emitting it.
Built for first-party ingestion — molecule.dev's own tooling (the mlcl
CLI, the molecule-mcp server) uses this bond to emit usage telemetry into
the platform's existing analytics pipeline, dogfooding the same
@molecule/api-analytics interface scaffolded apps consume.
Quick Start
import { setProvider } from '@molecule/api-analytics'
import { createHttpAnalyticsProvider } from '@molecule/api-analytics-http'
setProvider(
createHttpAnalyticsProvider({
url: 'https://api.molecule.dev/v1/telemetry/cli',
// Optional request tagging — defaults to 'unknown'.
source: 'mlcl',
}),
)
// track() now POSTs; failures are swallowed.
Type
provider
Installation
npm install @molecule/api-analytics-http @molecule/api-analytics
API
Interfaces
HttpAnalyticsProviderOptions
Options for createHttpAnalyticsProvider.
interface HttpAnalyticsProviderOptions {
/** Endpoint that receives the POSTs. Defaults to $MOLECULE_ANALYTICS_URL; no-op when neither is set. */
url?: string
/** Tag identifying the emitting tool ('mlcl', 'molecule-mcp', …). Defaults to 'unknown'. */
source?: string
/** Best-effort error observer (e.g. debug logging). Never throws. */
onError?: (error: unknown) => void
/** Abort emit after this many ms (default 5000). */
timeoutMs?: number
}
Functions
createHttpAnalyticsProvider(options?)
Build an HTTP analytics provider bound to the configured endpoint.
function createHttpAnalyticsProvider(options?: HttpAnalyticsProviderOptions): AnalyticsProvider
Core Interface
Implements @molecule/api-analytics interface.
Injection Notes
Runtime Dependencies
@molecule/api-analytics
Wire format — one JSON object per call:
{
"kind": "track", // "track" | "identify" | "page" | "group"
"source": "mlcl", // the `source` option
"sentAt": "2026-09-19T…", // ISO timestamp
"event": { /* the AnalyticsEvent / user / pageView as given *\/ }
}
The receiving endpoint is expected to validate and allowlist (never trust a
remote client's event names) — see molecule-dev's /v1/telemetry/cli route
for the reference implementation. POSTs carry a 5-second timeout; a slow or
dead endpoint costs at most one aborted request, never a hang.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Each user action the app cares about (signup/login, a page or screen
view, a purchase/conversion or other domain event) EMITS a
track()/page()with the RIGHT event name + properties when performed in the UI — and the event is actually RECORDED, proven by reading it back, not by finding atrack(...)call in source. Analytics is fire-and-forget to a third-party (Mixpanel/PostHog/Segment) that is NOT reachable in the sandbox and NOT captured byread_activity(that captures email/sms/push/webhook/ channel, never analytics). So prove the wiring locally: read the event back from the app's own events store/dashboard/funnel if it has one, or bond a local recording/console provider for dev and confirm the call fired viaread_logs. - Events are ATTRIBUTED to the right identity:
identify({ userId, ... })runs at signup/login so events carry thatuserId. Server-side has no ambient session — atrack()/page()with neitheruserIdnoranonymousIdcollapses every user into one shared "anonymous" identity. If the app tracks activity before login, confirm thoseanonymousIdevents associate/merge to the user on identify. - Tracking is NON-BLOCKING. Unlike the app-side bond, these SERVER
functions PROPAGATE provider failures (they reject when the provider does,
and throw when none is bonded), so fire-and-forget is the CALLER's job:
every call site wraps the call in
.catch()/ log-and-continue. Verify by forcing a failure (unbonded or erroring provider) — the user action (signup, checkout) still completes with no visible error and no added latency; the analytics await never blocks the response. - PRIVACY — no secret or sensitive PII in event
propertiesor identifytraits: never a password, full card/PAN, CVV, auth token, or session cookie. Read the recorded event back and confirm only non-sensitive attributes (plan name, amount, item id) are present. - If the app exposes analytics reports/dashboards/funnels, the numbers
reflect REAL recorded events (perform an action, the matching count/funnel
step increments) and are SCOPED — a user/org reads only its OWN analytics;
no endpoint lets one org read another org's numbers (
group()associates a user under the bond's group type, 'company' by default).