@sentientui/core
v0.8.0
Published
Framework-agnostic JavaScript SDK for [SentientUI](https://sentient-ui.com) — a Thompson Sampling bandit + persona/portrait engine that automatically surfaces the best-performing variant for each visitor. Learning runs on the SentientUI hosted API.
Downloads
1,785
Readme
@sentientui/core
Framework-agnostic JavaScript SDK for SentientUI — a Thompson Sampling bandit + persona/portrait engine that automatically surfaces the best-performing variant for each visitor. Learning runs on the SentientUI hosted API.
Most users should install
@sentientui/reactinstead — it bundles this package and adds the SSR-safe<AdaptiveRoot>,<Adaptive>, and hooks. Use@sentientui/coredirectly only if you are not building with React.
Installation
npm install @sentientui/coreQuick start
import { init } from '@sentientui/core';
const client = init({
apiKey: 'pk_your_key', // from sentient-ui.com → Settings
context: 'saas', // 'landing' | 'ecommerce' | 'saas' | 'marketplace'
});
// Get a variant assignment for a component (returns null during SSR)
const result = await client.assign('hero_headline', ['control', 'variant_b']);
console.log(result?.variantId); // e.g. 'variant_b'
console.log(result?.content); // managed text content if the variant is WYSIWYG-managed
// Fire a goal (reward) when the visitor converts
client.goal('trial_started', { plan: 'pro' });init() returns a no-op client during SSR (typeof window === 'undefined'), when consent is false, or when apiKey does not start with pk_. The hosted ingest URL (https://api.sentient-ui.com/v1/events) is built in — no URL configuration required.
API
init(config) → SentientClient
| Option | Type | Description |
|--------|------|-------------|
| apiKey | string | Public API key (pk_…) from the SentientUI dashboard. |
| context | 'landing' \| 'ecommerce' \| 'saas' \| 'marketplace' | Type of product. Used for segment weighting and analytics grouping. |
| consent | boolean (default true) | When false, returns a no-op client (no cookies, no events). |
| initialAssignments | Record<string, string> | SSR-preloaded assignments. Seeds the cache so assign() returns immediately for listed components. |
| sessionSegment | string | Segment from SSR (device:source). Must match the value used in preloadAssignments. |
| userId | string | Optional cross-session identity. Persists portraits across sessions for the same user. |
| debug | boolean | Logs events to the console and exposes window.__sentient. |
client.assign(componentId, variantIds?) → Promise<AssignResult | null>
Asks the hosted bandit for a variant. Cached locally per (componentId, segment) — repeat calls hit the cache. Returns null during SSR or when the session has no ID.
type AssignResult = {
variantId: string;
assignmentTtlMs: number;
content?: string; // populated when the variant is dashboard-managed (WYSIWYG)
};client.track(event)
Queues an event for batched ingest. Events flush every 5 s and on visibilitychange / page unload (via fetch with keepalive: true).
client.goal(name, metadata?, weight?, stepIndex?)
Fires a named goal for the current session. Used for cross-component conversions (e.g. 'trial_started', 'purchase_completed') where you cannot scope the reward to a single <Adaptive>.
Two reward paths:
client.goal()sends a named reward event that the bandit attributes to whatever variant was active at goal time. When using@sentientui/react, the<Adaptive>component'sonConvertprop (or thegoalprop on<AdaptiveText>) is the preferred path for rewards scoped to a single component — it ties the reward directly to the assignment without needing the component ID. Useclient.goal()for funnel steps that span multiple components or occur after navigation.
weight(0–1, default1.0) — partial reward value. Use values < 1 for funnel steps that precede the final conversion. The bandit learns from each step immediately.stepIndex(default0) — position in the funnel for analytics grouping.
client.identify(userId)
Attaches a stable user ID to the session. Portraits and cluster assignment carry forward across future sessions for the same userId.
client.getAssignment(componentId, segment)
Synchronously returns the cached assignment, or null if not yet assigned. Use when you need a non-async lookup.
client.getGraph()
Returns the current GraphSnapshot (page nodes captured by the optional graph scanner — see @sentientui/core/graph).
client.destroy()
Flushes the event queue and clears session state. Call on page unload if you need a synchronous teardown (the SDK already handles visibilitychange automatically).
SSR helpers
import { preloadAssignments, readSessionCookie } from '@sentientui/core/server';
// In your server loader / getServerSideProps / Server Component.
// `cookies` must expose `get(name)` — Next.js `req.cookies`, `headers().cookies()`, or any
// object with the same shape.
const sessionId = readSessionCookie(cookies) ?? crypto.randomUUID();
const initialAssignments = await preloadAssignments(
[
{ id: 'hero_headline', variantIds: ['control', 'variant_b'] },
{ id: 'pricing_cta', variantIds: ['monthly', 'annual_first'] },
],
sessionId,
{
apiKey: process.env.SENTIENT_API_KEY!,
baseUrl: 'https://api.sentient-ui.com/v1',
origin: process.env.APP_ORIGIN, // must be in the project's allowed origins
userAgent, // from request headers, aligns segment with the client
referer,
},
);Pass initialAssignments and the same sessionSegment to init() on the client to prevent hydration mismatches.
For pages with a section layout, use preloadDecisions instead — same options, plus a sections: string[] request field. The return value carries both assignments and layoutOrder.
Optional: graph mode
@sentientui/core/graph is a separate, tree-shakable entry that activates the DOM scanner and graph sync (component-to-component edges + 2-hop reward propagation). Import it once, on the client, after init():
import('@sentientui/core/graph');The lean bundle stays under 8 KB gzip; graph adds roughly another 8 KB.
Local overrides (development)
# URL parameter (stackable)
https://yourapp.com?sentient_variant=hero_cta:variant_a
# Or before SDK init:
window.__sentient_overrides = { hero_cta: 'variant_a' };Overrides bypass the bandit entirely — no events recorded.
Docs
Full reference: sentient-ui.com/docs.
License
MIT
