@arial-ai/analytics
v0.0.4
Published
Official Arial analytics SDK — submit taxonomy-validated product events from browser, Node 20+, Bun, Deno, and edge runtimes.
Maintainers
Readme
@arial-ai/analytics
Official analytics SDK for Arial. Ships taxonomy-validated
product events to events.arial.sh from browser, Node 20+, Bun, Deno, and
edge runtimes (Cloudflare Workers, Vercel Edge).
- Typed — autocomplete over every canonical event name + property
- Validated — bad events fail fast client-side, never wasting a round-trip
- Isomorphic — one bundle, works everywhere
fetchexists - Safe by default — never throws into your app; all operational errors
surface through a single
onErrorcallback - Tiny surface — one factory, eight methods, one error class
Install
npm install @arial-ai/analytics
# or: pnpm add, yarn add, bun addQuickstart
import { createArial } from "@arial-ai/analytics";
const arial = createArial({
writeKey: "wk_...", // from signup — safe to embed in browser/client
workspaceId: "wsp_...", // from signup
onError: (err) => console.warn("[arial]", err.code, err.message),
});
arial.identify("user_42", { plan: "pro" });
arial.track("user.signed_in", { method: "google" });
arial.page({ path: "/dashboard", title: "Dashboard" });
// Before a serverless function exits, or on app teardown:
await arial.shutdown();That's it. Events are batched and sent to https://events.arial.sh/v1/events
authenticated by Authorization: Bearer <writeKey>. Get both the key and the
workspace id from POST /v1/workspaces/signup.
The signup response returns two credentials:
writeKey(wk_…) — for this SDK. Scoped to writing events only. Safe to embed in browser/mobile/client code.agentKey(agk_…) — for the control-plane API (CLI, MCP, servers). Broad privileges. Never ship this to browsers.
Configuration
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| writeKey | string | — | Required. Write key (wk_…) from signup. Sent as Authorization: Bearer …. Safe to embed in browser/mobile/client code. |
| workspaceId | string | — | Required. Your Arial workspace id. Must match the workspace bound to writeKey. |
| host | string | https://events.arial.sh | Override for self-hosting / staging. |
| flushAt | number | 20 | Flush when the queue reaches this many events. Hard cap: 500 per request. |
| flushInterval | number (ms) | 5000 | Timer-based flush. 0 disables the timer — call flush() yourself. |
| maxQueueSize | number | 10_000 | Queue cap. Overflow drops oldest + fires QUEUE_OVERFLOW. |
| maxRetries | number | 5 | Retries on network error / 429 / 5xx (full-jitter exponential backoff). |
| platform | "web" \| "ios" \| "android" \| "server" | auto-detected | Forced platform tag on every envelope. |
| fetch | typeof fetch | global fetch | Injectable for tests or custom polyfills. |
| onError | (err: ArialError) => void | no-op | Only surface for operational errors (see below). |
| disableAutoContext | boolean | false | Skip URL / UA / UTM capture in the browser. |
| registerProcessHooks | boolean | false | Node: auto-shutdown() on beforeExit / SIGINT / SIGTERM. |
| debug | boolean | false | Verbose logging to console. |
Browser vs. server
Same API, different auto-capture. The SDK detects the runtime and fills in what it can:
| | browser | server (Node, Bun, Deno, edge) |
| --------------------- | -------------------------------------- | ----------------------------------------------- |
| platform | "web" | "server" |
| url | window.location.href | — (pass via per-call override if you want it) |
| user_agent | navigator.userAgent | — (same) |
| UTM params | parsed from location.search | — (pass via track() options) |
| anonymous_id | persisted in localStorage["ar_aid"] | process-lifetime in-memory |
| session_id | per tab in sessionStorage["ar_sid"] | process-lifetime in-memory, 30-min rollover |
Canonical events
track(event, properties) is typed against the canonical taxonomy.
Autocomplete works out of the box:
arial.track("user.signed_in", { method: "google" }); // ✓
arial.track("feature.used", { // ✓
feature_id: "dashboard-filter",
feature_role: "core",
outcome: "success",
});
arial.track("not.an.event", {}); // ✗ Type error
arial.track("user.signed_in", {}); // ✗ Validation error at runtime
// → onError({ code: "VALIDATION_FAILED" })Full taxonomy reference: events.arial.sh/docs/taxonomy.json.
Arial is canonical-only: any event name outside the taxonomy is
rejected at ingest. For product-specific action, use feature.used
with an agent-chosen feature_id and the right feature_role. See
ADR 0007.
Identity
arial.identify("user_42", { plan: "pro" }); // POST /v1/identify + setUser
arial.setUser("user_42"); // set id only, no network
arial.setAccount("acct_99"); // tenant / workspace id
arial.reset(); // on logout: rotate ids, forget usertraitsare stored server-side and never ride on event envelopes (see ADR 0004 — Invariant 6: no PII on events).reset()rotatesanonymous_idandsession_idand forgets the user. Call it when the user signs out.
Batching & performance
- Synchronous
track()— returns immediately. Queue push + validation only. flushAt(default 20) triggers a size-based flush.flushInterval(default 5000ms) triggers a time-based flush.- Chunks >500 events are split into multiple POSTs automatically.
- Concurrent in-flight batches are allowed so a slow retry doesn't block new events.
- The flush timer uses
setTimeout.unref()on Node — it will not keep a process alive by itself.
Error handling
Every operational error fires onError. track() / flush() / shutdown()
never throw. createArial() throws only on config mistakes.
import { type ArialError } from "@arial-ai/analytics";
createArial({
writeKey: "wk_...",
workspaceId: "wsp_...",
onError: (err: ArialError) => {
switch (err.code) {
case "VALIDATION_FAILED": // event rejected against taxonomy
case "QUEUE_OVERFLOW": // dropped oldest due to maxQueueSize
case "NETWORK_ERROR": // fetch threw, retries exhausted
case "HTTP_ERROR": // 4xx from ingest (terminal)
case "INVALID_RESPONSE": // 2xx but unparseable body
case "SHUTDOWN": // track() called after shutdown()
case "IDENTIFY_NOT_IMPLEMENTED": // ingest returned 404 / 501
myObservability.log(err);
}
},
});Shutdown (Lambda / Worker finalizers)
In short-lived runtimes, always await arial.shutdown() before exit or
in-flight events may be lost:
export async function handler(event) {
const arial = createArial({ writeKey: "wk_...", workspaceId: "wsp_..." });
try {
arial.track("user.signed_in", { method: "google" });
return myHandler(event);
} finally {
await arial.shutdown();
}
}On Node, opt into automatic shutdown on process exit:
createArial({ writeKey: "wk_...", workspaceId: "wsp_...", registerProcessHooks: true });Runtime matrix
| Runtime | Minimum | Tested | | --- | --- | --- | | Node.js | 20.x | ✓ | | Bun | 1.x | ✓ | | Deno | 1.40 | ✓ | | Chromium | 90 | ✓ | | Firefox | 90 | ✓ | | Safari | 15 | ✓ | | Cloudflare Workers | current | ✓ | | Vercel Edge | current | ✓ |
Requires fetch, crypto.randomUUID (polyfilled if missing), and Promise.
Not in scope (follow-ups)
identify()persistence in the ingest service — today it is a 202 no-op.- Offline queue (localStorage / IndexedDB) for flaky networks.
sendBeaconfallback onvisibilitychange.- React / Next helpers — shipping separately as
@arial-ai/react. - Mobile SDKs.
License
MIT © Arial
