Public API#

Docs index - package README

The default export of @revu-ai/core is a singleton bound to a single client. Every public method is wrapped so internal errors are swallowed and (in debug: true) logged. The SDK never throws into the host page.

JSDoc in src/types.js is the source of truth for parameter and return types. This page documents semantics, examples, and edge cases.

Contents#

revu.init(config)#

Initialize the SDK. Safe to call once per page. Subsequent calls are ignored (the first config wins).

revu.init({
  apiKey: "revu_pk_your_write_key", // required
  environment: "production", // optional
  cookieDomain: ".example.com", // share visitor across subdomains
});

Every other option is documented in configuration.md.

Edge cases.

  • Missing or non-string apiKey is a configuration error. In production the SDK fails closed (no events ship). With debug: true the error is logged with a [REVU] prefix.
  • Invalid host (not a string, not http or https, malformed URL) is also a configuration error and fails closed.
  • Plugins registered via revu.use(...) before init() are queued and installed when init() runs.

revu.capture(eventType, properties?)#

Record an explicit event. Use this for signals autocapture cannot see: server-confirmed completions, async results, wizard steps that do not change the URL.

revu.capture("checkout_completed", {
  plan: "pro",
  amount_cents: 4900,
  currency: "USD",
});
revu.capture("report_exported", { format: "pdf", pages: 12 });

Property values. Properties are sanitized to a JSON-safe shape at the source before the event is queued, so a stray value can never break the transport:

  • Keep: strings, finite numbers, booleans, null, and nested plain objects / arrays of those.
  • Dropped: functions, symbols, BigInt, undefined, and circular references (the cycle is dropped, the rest of the object is kept). A non-finite number (NaN, Infinity) becomes null, matching JSON.
  • Depth: nesting is kept up to 6 levels; anything deeper is dropped. Event properties are meant to be shallow, but this is worth knowing if you pass a deeply nested object - flatten what you need before sending.

Edge cases.

  • An empty or non-string eventType is silently ignored.
  • Properties you pass always win over engine-attached context on collision, so a host that knows better can override anything the SDK auto-populates.
  • For interactions autocapture already names (a button click, a form submit, a pageview), prefer letting the server-side feature catalog group them. Calling capture() on top duplicates the signal.

revu.identify(userId)#

Replace the current user_id with your authoritative auth id (call on login or register).

revu.identify("u_4b9a2");

Emits a synthetic $identify event so the dashboard can mark the exact moment the visitor became known. If a prior user_id existed, the event carries properties.previous_user_id so the transition is visible on the timeline.

Edge cases.

  • Idempotent: calling identify repeatedly with the same id is a no-op (no duplicate $identify events).
  • Empty string or non-string id is a no-op.
  • The id is persisted, so a reload restores it without a second call.

See also: concepts.md - anonymous_id vs user_id.

revu.alias(authoritativeId)#

Join the current device's identity to a separate, authoritative identity for the same person, without changing the local user_id.

The motivating flow is cross-device. A visitor signs up on desktop and gets a magic link by email. They open the link on their phone, which has its own auto-assigned user_id. After auth resolves on the phone, call:

revu.alias("u_4b9a2");

The server records that the phone's current id and u_4b9a2 are the same human, and dashboards stitch both journeys. The phone's local user_id stays the same so already-queued events still ship under it.

Edge cases.

  • Idempotent: the server upserts on (organization, alias_user_id), so repeated calls produce one mapping, not duplicates.
  • Empty string or non-string id is a no-op.
  • No-op when authoritativeId already equals the current local id.

revu.reset()#

Sign-out counterpart to identify. Emits a $reset event marking the end of the identified session, then clears the user id and rotates the session id. The anonymous device id is preserved.

function onLogout() {
  revu.reset();
}

Order matters. The $reset event ships with the OLD session_id and user_id, so it sorts as the final marker of the logged-in session on the timeline. Subsequent events use a fresh session id with user_id: null (or a fresh auto id when autoIdentify is on).

Edge cases.

  • No-op when there is no identified user. A redundant sign-out path (multiple components calling reset on logout) will not accidentally rotate the session for an anonymous visitor.
  • The anonymous_id is never rotated by reset(). The browser remains a known device.

revu.optOut() / revu.optIn() / revu.hasOptedOut()#

The master capture switch. A cookie banner routes its state through these rather than wrapping every call in a consent check.

revu.optOut();        // stop all capture (reject / withdraw consent)
revu.optIn();         // resume capture (accept)
revu.hasOptedOut();   // -> boolean

While opted out, every interaction (autocapture, pageviews, custom capture() calls, identity events) is suppressed before an event is built, so nothing leaves the browser. The choice is persisted in the same first-party store as identity, so a reload honors it without re-prompting.

These are aliases for the analytics consent category: optOut() is consent.set({ analytics: "denied" }) and optIn() is consent.set({ analytics: "granted" }). Use them for a simple binary banner; use consent.set() when you need per-category control.

Behavior.

  • Opting out does not clear identity. Opting back in resumes the same visitor; call revu.reset() if you want a clean break instead.
  • optOut() stops new capture but leaves events already queued under prior consent to flush. To also discard locally-buffered events, see Privacy and data.
  • hasOptedOut() returns false before init().

revu.consent.set() / revu.consent.get()#

Per-category consent control. The SDK understands three categories - analytics, marketing, and functional - each "granted" or "denied".

// Map a cookie banner's result straight through (partial maps merge):
revu.consent.set({ analytics: "granted", marketing: "denied" });

revu.consent.get();
// -> { analytics: "granted", marketing: "denied", functional: "granted" }

Only analytics gates capture: denying it suppresses every event, exactly like optOut(). marketing and functional are declarative - the SDK stamps the full state on every event as context.consent so the server can honor the visitor's choices on downstream destinations, but it never acts on them itself.

Behavior.

  • set() merges a partial map over the current state and persists the result in the same first-party store as identity. Unknown categories and values other than "granted" / "denied" are ignored.
  • get() returns a copy of the current state. Before init(), set() is a no-op and get() returns the all-granted default.
  • A pre-existing binary opt-out from an earlier SDK version is read on the first load after upgrade, so a prior reject keeps being honored.
  • See Global Privacy Control for how the honorGpc option feeds this state.

revu.flush()#

Send any buffered events now. Returns a promise that resolves to true when the batch is accepted, false if the send fails, and undefined if called before init().

await revu.flush();

You rarely need to call this. Events flush automatically on a size threshold (flushAt, default 20), on an interval (flushIntervalMs, default 5000 ms), when connectivity returns (online), and on page hide. Manual flush() is useful when you are about to do something destructive (full reload, navigate away to a non-instrumented page) and want the buffer drained first.

revu.use(plugin)#

Register a plugin. Equivalent to passing it via init({ plugins: [...] }), but available as a separate call so plugins can be wired conditionally (behind a feature flag, after a config fetch).

import revu from "@revu-ai/core";
import { myPlugin } from "./my-plugin.js"; // your own plugin, see plugins.md

revu.use(myPlugin());
revu.init({ apiKey: "revu_pk_..." });

Pre-init use() calls are queued and drained when init() runs. The same plugin name registered twice is a no-op so a redundant wiring path does not cause double listeners.

See plugins.md for the plugin contract.

revu.version#

The build string of the bundle (for example "0.1.0"). The same string ships on every event as context.sdk_version, and it is useful for support tickets (paste it in) and console introspection.

console.log(revu.version);

RevuClient#

The named export RevuClient is the class behind the singleton. The singleton is the right answer for almost every host: it removes a layer of wiring and guarantees one source of identity per page. Construct RevuClient directly only when you genuinely need multiple isolated clients in the same page (a rare case, typically embedded analytics in a multi-tenant editor).

import { RevuClient, VERSION } from "@revu-ai/core";

const client = new RevuClient({
  apiKey: "revu_pk_...",
  host: "https://api.revu.ai",
  /* every option from configuration.md */
});
client.start();
client.capture("event_name", { foo: "bar" });

RevuClient's public methods are start, capture, identify, alias, reset, optOut, optIn, hasOptedOut, setConsent, getConsent, flush, and use. The capture and identity methods mirror the singleton (the singleton calls start() for you inside init()); they are not wrapped with the safe() boundary, so a host using the class directly is responsible for catching errors at their own boundary.