@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)
Section titled “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
Section titled “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
Section titled “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?)
Section titled “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)
Section titled “updateTraits(traits)”Merges new traits into the existing traits without requiring a full identify() call.
client.updateTraits({ plan: 'enterprise', locale: 'fr' })track(event, properties?)
Section titled “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)
Section titled “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)
Section titled “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()
Section titled “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()
Section titled “destroy()”Tears down the client. Cancels pending timers and unsubscribes from state changes.
client.destroy()AdaptiveStoreState
Section titled “AdaptiveStoreState”interface AdaptiveStoreState { context: UserContext isReady: boolean}UserContext
Section titled “UserContext”interface UserContext { traits: UserTraits signals: BehavioralSignals maturity: Maturity}UserTraits
Section titled “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
Section titled “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
Section titled “Maturity”type Maturity = 'new' | 'onboarding' | 'active' | 'power' | 'dormant'MaturityConfig
Section titled “MaturityConfig”interface MaturityConfig { newMaxSessions: number // Default: 3 onboardingMaxSessions: number // Default: 10 powerMinFeatures: number // Default: 5 dormantDaysInactive: number // Default: 14}AdaptationRule
Section titled “AdaptationRule”interface AdaptationRule { id: string adaptationId: string priority: number conditions: ConditionGroup action: AdaptationAction}Condition
Section titled “Condition”interface Condition { field: string operator: ConditionOperator value: unknown}ConditionOperator
Section titled “ConditionOperator”type ConditionOperator = | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'notContains' | 'exists' | 'notExists' | 'between' | 'matches'ConditionGroup
Section titled “ConditionGroup”interface ConditionGroup { all?: (Condition | ConditionGroup)[] any?: (Condition | ConditionGroup)[] not?: Condition | ConditionGroup}AdaptationAction
Section titled “AdaptationAction”type AdaptationAction = | { type: 'show'; variantId: string } | { type: 'hide' } | { type: 'reorder'; order: string[] } | { type: 'modify'; props: Record<string, unknown> }AdaptationPoint
Section titled “AdaptationPoint”interface AdaptationPoint { id: string variants: readonly string[] defaultVariant: string strategy: Strategy}ResolvedAdaptation
Section titled “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
Section titled “Strategy”type Strategy = { type: 'rules' }TrackerAdapter
Section titled “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
Section titled “TrackerEvent”interface TrackerEvent { name: string properties?: Record<string, unknown> timestamp: number userId?: string sessionId: string}BuiltInTrackerConfig
Section titled “BuiltInTrackerConfig”interface BuiltInTrackerConfig { maxEvents: number // Default: 1000 maxAgeMs: number // Default: 2592000000 (30 days)}StorageDriver
Section titled “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)
Section titled “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()
Section titled “createMemoryDriver()”Creates an in-memory StorageDriver. Data is lost when the process exits.
import { createMemoryDriver } from '@presage-kit/core'
const driver = createMemoryDriver()RulesEngine
Section titled “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)
Section titled “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)
Section titled “addRules(rules)”Adds multiple rules at once. Re-sorts once.
engine.addRules([rule1, rule2, rule3])removeRule(ruleId)
Section titled “removeRule(ruleId)”Removes a rule by its ID.
engine.removeRule('my-rule')evaluate(adaptationId, context)
Section titled “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()
Section titled “getRules()”Returns all registered rules as a readonly array.
const rules = engine.getRules() // readonly AdaptationRule[]atom(initialValue)
Section titled “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>
Section titled “ReadableAtom<T>”interface ReadableAtom<T> { get(): T subscribe(listener: Listener<T>): Unsubscribe listen(listener: Listener<T>): Unsubscribe}WritableAtom<T>
Section titled “WritableAtom<T>”Extends ReadableAtom with a set() method.
interface WritableAtom<T> extends ReadableAtom<T> { set(value: T): void}Listener<T>
Section titled “Listener<T>”type Listener<T> = (value: T, oldValue: T) => voidUnsubscribe
Section titled “Unsubscribe”type Unsubscribe = () => void