@presage-kit/core
The core package provides the adaptive client, rules engine, tracker, persistence drivers, and all shared types.
pnpm add @presage-kit/corecreateAdaptiveClient(config)
Sección titulada «createAdaptiveClient(config)»Creates an AdaptiveClient instance — the central hub for rules, tracking, and context management.
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({ rules: [/* ... */], tracker: { adapters: [/* ... */], builtIn: { maxEvents: 500 }, }, persistence: { driver: createLocalStorageDriver('my-app'), }, maturity: { newMaxSessions: 5, dormantDaysInactive: 21, },})AdaptiveClientConfig
Sección titulada «AdaptiveClientConfig»interface AdaptiveClientConfig { rules?: AdaptationRule[] tracker?: { adapters?: TrackerAdapter[] builtIn?: Partial<BuiltInTrackerConfig> } persistence?: { driver: StorageDriver } maturity?: Partial<MaturityConfig>}| Field | Type | Default | Description |
|---|---|---|---|
rules | AdaptationRule[] | [] | Initial set of adaptation rules |
tracker.adapters | TrackerAdapter[] | [] | External analytics adapters |
tracker.builtIn.maxEvents | number | 1000 | Maximum events in memory |
tracker.builtIn.maxAgeMs | number | 2592000000 (30 days) | Max event age before pruning |
persistence.driver | StorageDriver | null | Storage driver for traits and signals |
maturity | Partial<MaturityConfig> | See defaults | Maturity threshold overrides |
AdaptiveClient
Sección titulada «AdaptiveClient»interface AdaptiveClient { state: ReadableAtom<AdaptiveStoreState> identify(userId: string, traits?: UserTraits): void updateTraits(traits: Partial<UserTraits>): void track(event: string, properties?: Record<string, unknown>): void resolve(point: AdaptationPoint): ResolvedAdaptation evaluateAction(adaptationId: string): AdaptationAction | null getContext(): UserContext destroy(): void}A reactive atom containing the current store state. Subscribe to it for change notifications.
const unsubscribe = client.state.subscribe((state) => { console.log('Context changed:', state.context)})identify(userId, traits?)
Sección titulada «identify(userId, traits?)»Associates the current session with a user. Merges provided traits with existing traits.
client.identify('user-123', { role: 'admin', plan: 'enterprise', signupDate: '2025-01-15', company: 'Acme Corp', companySize: 150,})Also forwards the identify call to all registered tracker adapters.
updateTraits(traits)
Sección titulada «updateTraits(traits)»Merges new traits into the existing traits without requiring a full identify() call.
client.updateTraits({ plan: 'enterprise', locale: 'fr' })track(event, properties?)
Sección titulada «track(event, properties?)»Records an event. Triggers debounced signal recomputation (100ms).
client.track('feature_used', { featureId: 'export' })client.track('click', { elementId: 'sidebar-settings' })client.track('custom_signal', { signalId: 'engagement', value: 3 })client.track('page_view', { path: '/dashboard' })resolve(point)
Sección titulada «resolve(point)»Resolves an adaptation point by evaluating rules and returning the selected variant.
const result = client.resolve({ id: 'onboarding', variants: ['guided-tour', 'standard', 'minimal'], defaultVariant: 'standard', strategy: { type: 'rules' },})
console.log(result.selectedVariant) // 'guided-tour'console.log(result.reason) // 'Rule "new-user-tour" matched'evaluateAction(adaptationId)
Sección titulada «evaluateAction(adaptationId)»Evaluates rules for an adaptation point and returns the raw action (or null if no rule matches). Useful for imperative control flow.
const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'reorder') { // Reorder navigation items}getContext()
Sección titulada «getContext()»Returns the current UserContext snapshot.
const ctx = client.getContext()console.log(ctx.traits.role) // 'admin'console.log(ctx.signals.sessionCount) // 12console.log(ctx.maturity) // 'active'destroy()
Sección titulada «destroy()»Tears down the client. Cancels pending timers and unsubscribes from state changes.
client.destroy()AdaptiveStoreState
Sección titulada «AdaptiveStoreState»interface AdaptiveStoreState { context: UserContext isReady: boolean}UserContext
Sección titulada «UserContext»interface UserContext { traits: UserTraits signals: BehavioralSignals maturity: Maturity}UserTraits
Sección titulada «UserTraits»interface UserTraits { userId?: string role?: string plan?: string signupDate?: string company?: string companySize?: number locale?: string [key: string]: unknown}Extensible with any custom key-value pair.
BehavioralSignals
Sección titulada «BehavioralSignals»interface BehavioralSignals { sessionCount: number totalEvents: number featureUsage: Record<string, number> lastSeenAt: string // ISO 8601 timestamp firstSeenAt: string // ISO 8601 timestamp currentSessionDuration: number daysSinceSignup: number clickMap: Record<string, number> customSignals: Record<string, number>}Maturity
Sección titulada «Maturity»type Maturity = 'new' | 'onboarding' | 'active' | 'power' | 'dormant'MaturityConfig
Sección titulada «MaturityConfig»interface MaturityConfig { newMaxSessions: number // Default: 3 onboardingMaxSessions: number // Default: 10 powerMinFeatures: number // Default: 5 dormantDaysInactive: number // Default: 14}AdaptationRule
Sección titulada «AdaptationRule»interface AdaptationRule { id: string adaptationId: string priority: number conditions: ConditionGroup action: AdaptationAction}Condition
Sección titulada «Condition»interface Condition { field: string operator: ConditionOperator value: unknown}ConditionOperator
Sección titulada «ConditionOperator»type ConditionOperator = | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'notContains' | 'exists' | 'notExists' | 'between' | 'matches'ConditionGroup
Sección titulada «ConditionGroup»interface ConditionGroup { all?: (Condition | ConditionGroup)[] any?: (Condition | ConditionGroup)[] not?: Condition | ConditionGroup}AdaptationAction
Sección titulada «AdaptationAction»type AdaptationAction = | { type: 'show'; variantId: string } | { type: 'hide' } | { type: 'reorder'; order: string[] } | { type: 'modify'; props: Record<string, unknown> }AdaptationPoint
Sección titulada «AdaptationPoint»interface AdaptationPoint { id: string variants: readonly string[] defaultVariant: string strategy: Strategy}ResolvedAdaptation
Sección titulada «ResolvedAdaptation»interface ResolvedAdaptation { adaptationId: string selectedVariant: string strategy: Strategy['type'] // 'rules' reason: string // e.g. 'Rule "my-rule" matched' or 'default' resolvedAt: number // timestamp}Strategy
Sección titulada «Strategy»type Strategy = { type: 'rules' }TrackerAdapter
Sección titulada «TrackerAdapter»Interface for plugging external analytics services into the tracker.
interface TrackerAdapter { name: string track(event: TrackerEvent): void | Promise<void> identify(userId: string, traits: UserTraits): void | Promise<void> flush?(): Promise<void>}TrackerEvent
Sección titulada «TrackerEvent»interface TrackerEvent { name: string properties?: Record<string, unknown> timestamp: number userId?: string sessionId: string}BuiltInTrackerConfig
Sección titulada «BuiltInTrackerConfig»interface BuiltInTrackerConfig { maxEvents: number // Default: 1000 maxAgeMs: number // Default: 2592000000 (30 days)}StorageDriver
Sección titulada «StorageDriver»Interface for custom persistence drivers.
interface StorageDriver { get<T>(key: string): T | null set<T>(key: string, value: T): void remove(key: string): void clear(): void}createLocalStorageDriver(prefix)
Sección titulada «createLocalStorageDriver(prefix)»Creates a StorageDriver backed by localStorage. Keys are stored as prefix:key.
import { createLocalStorageDriver } from '@presage-kit/core'
const driver = createLocalStorageDriver('my-app')// Stores as 'my-app:traits', 'my-app:signals'Gracefully returns null on get() when localStorage is unavailable (e.g., SSR).
createMemoryDriver()
Sección titulada «createMemoryDriver()»Creates an in-memory StorageDriver. Data is lost when the process exits.
import { createMemoryDriver } from '@presage-kit/core'
const driver = createMemoryDriver()Orderable
Sección titulada «Orderable»Base interface for items that can be reordered by reorderItems() and the AdaptiveOrder/useAdaptiveOrder APIs. Every item must have a unique id.
interface Orderable { id: string [key: string]: unknown}The NavItem interface used by AdaptiveNav/useAdaptiveNav extends Orderable.
reorderItems(items, order)
Sección titulada «reorderItems(items, order)»A pure utility function that reorders items according to an order array. Items listed in order appear first (in that order), followed by any remaining items not mentioned in order, preserving their original relative order.
import { reorderItems } from '@presage-kit/core'
const items = [ { id: 'a', label: 'Alpha' }, { id: 'b', label: 'Beta' }, { id: 'c', label: 'Gamma' },]
const reordered = reorderItems(items, ['c', 'a'])// [{ id: 'c', label: 'Gamma' }, { id: 'a', label: 'Alpha' }, { id: 'b', label: 'Beta' }]Parameters:
| Param | Type | Description |
|---|---|---|
items | T[] (extends Orderable) | Array of items to reorder |
order | string[] | Array of id values specifying the desired order |
Return type: T[] — A new array with items reordered. The original array is not mutated.
This is the same reorder logic used internally by AdaptiveOrder, AdaptiveNav, and useAdaptiveOrder.
RulesEngine
Sección titulada «RulesEngine»The rules engine can be used directly for testing or advanced use cases.
import { RulesEngine } from '@presage-kit/core'
const engine = new RulesEngine()addRule(rule)
Sección titulada «addRule(rule)»Adds a single rule. Re-sorts rules by priority.
engine.addRule({ id: 'my-rule', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }] }, action: { type: 'show', variantId: 'guided-tour' },})addRules(rules)
Sección titulada «addRules(rules)»Adds multiple rules at once. Re-sorts once.
engine.addRules([rule1, rule2, rule3])removeRule(ruleId)
Sección titulada «removeRule(ruleId)»Removes a rule by its ID.
engine.removeRule('my-rule')evaluate(adaptationId, context)
Sección titulada «evaluate(adaptationId, context)»Evaluates all rules for the given adaptation ID against a UserContext. Returns the first matching action, or null.
const action = engine.evaluate('onboarding', { traits: { role: 'admin' }, signals: { sessionCount: 2, totalEvents: 10, /* ... */ }, maturity: 'new',})getRules()
Sección titulada «getRules()»Returns all registered rules as a readonly array.
const rules = engine.getRules() // readonly AdaptationRule[]atom(initialValue)
Sección titulada «atom(initialValue)»A minimal reactive primitive for advanced use cases. Follows the Svelte store contract.
import { atom } from '@presage-kit/core'
const count = atom(0)
// Readconsole.log(count.get()) // 0
// Writecount.set(1)
// Subscribe (called immediately with current value)const unsub = count.subscribe((value, oldValue) => { console.log(`Changed from ${oldValue} to ${value}`)})
// Listen (NOT called immediately)const unlisten = count.listen((value, oldValue) => { console.log(`Changed from ${oldValue} to ${value}`)})
// Cleanupunsub()unlisten()ReadableAtom<T>
Sección titulada «ReadableAtom<T>»interface ReadableAtom<T> { get(): T subscribe(listener: Listener<T>): Unsubscribe listen(listener: Listener<T>): Unsubscribe}WritableAtom<T>
Sección titulada «WritableAtom<T>»Extends ReadableAtom with a set() method.
interface WritableAtom<T> extends ReadableAtom<T> { set(value: T): void}Listener<T>
Sección titulada «Listener<T>»type Listener<T> = (value: T, oldValue: T) => voidUnsubscribe
Sección titulada «Unsubscribe»type Unsubscribe = () => void